diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 0000000000..1627a59000 --- /dev/null +++ b/.bazelrc @@ -0,0 +1,14 @@ +build --experimental_guard_against_concurrent_changes + +build --strategy=Genrule=local +build --apple_platform_type=ios +build --cxxopt='-std=c++14' +build --copt='-w' +build --swiftcopt='-Xcc' +build --swiftcopt='-w' +build --spawn_strategy=local +build --strategy=SwiftCompile=local +build --features=debug_prefix_map_pwd_is_dot +build --features=swift.cacheable_swiftmodules +build --features=swift.debug_prefix_map + diff --git a/.gitignore b/.gitignore index 81a0b5a182..b0487bbc48 100644 --- a/.gitignore +++ b/.gitignore @@ -58,5 +58,9 @@ bazel-telegram-ios bazel-telegram-ios/* bazel-testlogs bazel-testlogs/* +*/*.swp +*.swp +build-input/data build-input/data/* +build-input/gen build-input/gen/* diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d89926d6ad..33f3b39ca5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -22,6 +22,10 @@ internal: - bash buildbox/deploy-telegram.sh hockeyapp environment: name: internal + artifacts: + paths: + - build/artifacts/Telegram.DSYMs.zip + expire_in: 1 week beta_testflight: tags: @@ -38,7 +42,7 @@ beta_testflight: artifacts: paths: - build/artifacts - expire_in: 1 week + expire_in: 3 weeks deploy_beta_testflight: tags: diff --git a/.gitmodules b/.gitmodules index c319a7c828..d416d6ad33 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,19 @@ [submodule "submodules/rlottie/rlottie"] path = submodules/rlottie/rlottie -url=../rlottie.git + url=../rlottie.git +[submodule "build-system/bazel-rules/rules_apple"] + path = build-system/bazel-rules/rules_apple + url=https://github.com/ali-fareed/rules_apple.git +[submodule "build-system/bazel-rules/rules_swift"] + path = build-system/bazel-rules/rules_swift + url = https://github.com/bazelbuild/rules_swift.git +[submodule "build-system/bazel-rules/apple_support"] + path = build-system/bazel-rules/apple_support + url = https://github.com/bazelbuild/apple_support.git +[submodule "submodules/TgVoip/libtgvoip"] + path = submodules/TgVoip/libtgvoip + url = https://github.com/telegramdesktop/libtgvoip.git +[submodule "build-system/tulsi"] + path = build-system/tulsi + url = https://github.com/ali-fareed/tulsi.git diff --git a/Telegram-iOS/ar.lproj/Localizable.strings b/Config/BUILD similarity index 100% rename from Telegram-iOS/ar.lproj/Localizable.strings rename to Config/BUILD diff --git a/Config/buck_rule_macros.bzl b/Config/buck_rule_macros.bzl index e597aa6ecd..0c101f023a 100644 --- a/Config/buck_rule_macros.bzl +++ b/Config/buck_rule_macros.bzl @@ -309,9 +309,9 @@ def glob_map(glob_results): result[file_name] = path return result -def glob_sub_map(prefix, glob_specs): +def glob_sub_map(prefix, glob_specs, exclude = []): result = dict() - for path in native.glob(glob_specs): + for path in native.glob(glob_specs, exclude = exclude): if not path.startswith(prefix): fail('\"%s\" does not start with \"%s\"' % (path, prefix)) file_key = path[len(prefix):] diff --git a/Config/objc_module_provider.bzl b/Config/objc_module_provider.bzl new file mode 100644 index 0000000000..a09f214a85 --- /dev/null +++ b/Config/objc_module_provider.bzl @@ -0,0 +1,31 @@ + +def _impl(ctx): + output_dir = ctx.attr.name + "_ModuleHeaders" + dir = ctx.actions.declare_directory(output_dir) + files = [] + files_command = "" + for file in ctx.files.headers: + outFile = ctx.actions.declare_file(output_dir + "/" + ctx.attr.module_name + "/" + file.basename) + files.append(outFile) + files_command = files_command + " && cp " + file.path + " " + outFile.path + ctx.actions.run_shell( + outputs = [dir] + files, + inputs = ctx.files.headers, + command = "mkdir -p " + dir.path + " " + files_command + ) + return [ + apple_common.new_objc_provider( + include_system = depset([dir.path]), + header = depset(files), + ), + ] + +objc_module = rule( + implementation = _impl, + attrs = { + "module_name": attr.string(mandatory = True), + "headers": attr.label_list( + allow_files = [".h"], + ), + }, +) diff --git a/Makefile b/Makefile index dc4224265b..a78940e107 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,12 @@ include Utils.makefile +APP_VERSION="5.16" +CORE_COUNT=$(shell sysctl -n hw.logicalcpu) +CORE_COUNT_MINUS_ONE=$(shell expr ${CORE_COUNT} \- 1) + BUCK_OPTIONS=\ - --config custom.appVersion="5.15.2" \ + --config custom.appVersion="${APP_VERSION}" \ --config custom.developmentCodeSignIdentity="${DEVELOPMENT_CODE_SIGN_IDENTITY}" \ --config custom.distributionCodeSignIdentity="${DISTRIBUTION_CODE_SIGN_IDENTITY}" \ --config custom.developmentTeam="${DEVELOPMENT_TEAM}" \ @@ -39,11 +43,30 @@ BUCK_OPTIONS=\ --config custom.developmentProvisioningProfileWatchExtension="${DEVELOPMENT_PROVISIONING_PROFILE_WATCH_EXTENSION}" \ --config custom.distributionProvisioningProfileWatchExtension="${DISTRIBUTION_PROVISIONING_PROFILE_WATCH_EXTENSION}" +BAZEL=$(shell which bazel) + +ifneq ($(BAZEL_CACHE_DIR),) + export BAZEL_CACHE_FLAGS=\ + --disk_cache="${BAZEL_CACHE_DIR}" +endif + +BAZEL_COMMON_FLAGS=\ + --announce_rc \ + --features=swift.use_global_module_cache \ + +BAZEL_DEBUG_FLAGS=\ + --features=swift.enable_batch_mode \ + --swiftcopt=-j${CORE_COUNT_MINUS_ONE} \ + +BAZEL_OPT_FLAGS=\ + --swiftcopt=-whole-module-optimization \ + --swiftcopt='-num-threads' --swiftcopt='16' \ + build_arm64: check_env $(BUCK) build \ - //:AppPackage#iphoneos-arm64 \ - //:Telegram#dwarf-and-dsym,iphoneos-arm64 \ + //Telegram:AppPackage#iphoneos-arm64 \ + //Telegram:Telegram#dwarf-and-dsym,iphoneos-arm64 \ //submodules/MtProtoKit:MtProtoKit#dwarf-and-dsym,shared,iphoneos-arm64 \ //submodules/MtProtoKit:MtProtoKit#shared,iphoneos-arm64 \ //submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#dwarf-and-dsym,shared,iphoneos-arm64 \ @@ -62,18 +85,18 @@ build_arm64: check_env //submodules/Display:Display#shared,iphoneos-arm64 \ //submodules/TelegramUI:TelegramUI#dwarf-and-dsym,shared,iphoneos-arm64 \ //submodules/TelegramUI:TelegramUI#shared,iphoneos-arm64 \ - //:WatchAppExtension#dwarf-and-dsym,watchos-arm64_32,watchos-armv7k \ - //:ShareExtension#dwarf-and-dsym,iphoneos-arm64 \ - //:WidgetExtension#dwarf-and-dsym,iphoneos-arm64 \ - //:NotificationContentExtension#dwarf-and-dsym,iphoneos-arm64 \ - //:NotificationServiceExtension#dwarf-and-dsym,iphoneos-arm64 \ - //:IntentsExtension#dwarf-and-dsym,iphoneos-arm64 \ + //Telegram:WatchAppExtension#dwarf-and-dsym,watchos-arm64_32,watchos-armv7k \ + //Telegram:ShareExtension#dwarf-and-dsym,iphoneos-arm64 \ + //Telegram:WidgetExtension#dwarf-and-dsym,iphoneos-arm64 \ + //Telegram:NotificationContentExtension#dwarf-and-dsym,iphoneos-arm64 \ + //Telegram:NotificationServiceExtension#dwarf-and-dsym,iphoneos-arm64 \ + //Telegram:IntentsExtension#dwarf-and-dsym,iphoneos-arm64 \ ${BUCK_OPTIONS} ${BUCK_RELEASE_OPTIONS} ${BUCK_THREADS_OPTIONS} ${BUCK_CACHE_OPTIONS} build_debug_arm64: check_env $(BUCK) build \ - //:AppPackage#iphoneos-arm64 \ - //:Telegram#dwarf-and-dsym,iphoneos-arm64 \ + //Telegram:AppPackage#iphoneos-arm64 \ + //Telegram:Telegram#dwarf-and-dsym,iphoneos-arm64 \ //submodules/MtProtoKit:MtProtoKit#dwarf-and-dsym,shared,iphoneos-arm64 \ //submodules/MtProtoKit:MtProtoKit#shared,iphoneos-arm64 \ //submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#dwarf-and-dsym,shared,iphoneos-arm64 \ @@ -92,12 +115,12 @@ build_debug_arm64: check_env //submodules/Display:Display#shared,iphoneos-arm64 \ //submodules/TelegramUI:TelegramUI#dwarf-and-dsym,shared,iphoneos-arm64 \ //submodules/TelegramUI:TelegramUI#shared,iphoneos-arm64 \ - //:WatchAppExtension#dwarf-and-dsym,watchos-arm64_32,watchos-armv7k \ - //:ShareExtension#dwarf-and-dsym,iphoneos-arm64 \ - //:WidgetExtension#dwarf-and-dsym,iphoneos-arm64 \ - //:NotificationContentExtension#dwarf-and-dsym,iphoneos-arm64 \ - //:NotificationServiceExtension#dwarf-and-dsym,iphoneos-arm64 \ - //:IntentsExtension#dwarf-and-dsym,iphoneos-arm64 \ + //Telegram:WatchAppExtension#dwarf-and-dsym,watchos-arm64_32,watchos-armv7k \ + //Telegram:ShareExtension#dwarf-and-dsym,iphoneos-arm64 \ + //Telegram:WidgetExtension#dwarf-and-dsym,iphoneos-arm64 \ + //Telegram:NotificationContentExtension#dwarf-and-dsym,iphoneos-arm64 \ + //Telegram:NotificationServiceExtension#dwarf-and-dsym,iphoneos-arm64 \ + //Telegram:IntentsExtension#dwarf-and-dsym,iphoneos-arm64 \ ${BUCK_OPTIONS} ${BUCK_DEBUG_OPTIONS} ${BUCK_THREADS_OPTIONS} ${BUCK_CACHE_OPTIONS} build_wallet_debug_arm64: check_env @@ -116,8 +139,8 @@ build_wallet_debug_arm64: check_env build_debug_armv7: check_env $(BUCK) build \ - //:AppPackage#iphoneos-armv7 \ - //:Telegram#dwarf-and-dsym,iphoneos-armv7 \ + //Telegram:AppPackage#iphoneos-armv7 \ + //Telegram:Telegram#dwarf-and-dsym,iphoneos-armv7 \ //submodules/MtProtoKit:MtProtoKit#dwarf-and-dsym,shared,iphoneos-armv7 \ //submodules/MtProtoKit:MtProtoKit#shared,iphoneos-armv7 \ //submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#dwarf-and-dsym,shared,iphoneos-armv7 \ @@ -136,18 +159,18 @@ build_debug_armv7: check_env //submodules/Display:Display#shared,iphoneos-armv7 \ //submodules/TelegramUI:TelegramUI#dwarf-and-dsym,shared,iphoneos-armv7 \ //submodules/TelegramUI:TelegramUI#shared,iphoneos-armv7 \ - //:WatchAppExtension#dwarf-and-dsym,watchos-armv7_32,watchos-armv7k \ - //:ShareExtension#dwarf-and-dsym,iphoneos-armv7 \ - //:WidgetExtension#dwarf-and-dsym,iphoneos-armv7 \ - //:NotificationContentExtension#dwarf-and-dsym,iphoneos-armv7 \ - //:NotificationServiceExtension#dwarf-and-dsym,iphoneos-armv7 \ - //:IntentsExtension#dwarf-and-dsym,iphoneos-armv7 \ + //Telegram:WatchAppExtension#dwarf-and-dsym,watchos-armv7_32,watchos-armv7k \ + //Telegram:ShareExtension#dwarf-and-dsym,iphoneos-armv7 \ + //Telegram:WidgetExtension#dwarf-and-dsym,iphoneos-armv7 \ + //Telegram:NotificationContentExtension#dwarf-and-dsym,iphoneos-armv7 \ + //Telegram:NotificationServiceExtension#dwarf-and-dsym,iphoneos-armv7 \ + //Telegram:IntentsExtension#dwarf-and-dsym,iphoneos-armv7 \ ${BUCK_OPTIONS} ${BUCK_DEBUG_OPTIONS} ${BUCK_THREADS_OPTIONS} ${BUCK_CACHE_OPTIONS} build: check_env $(BUCK) build \ - //:AppPackage#iphoneos-arm64,iphoneos-armv7 \ - //:Telegram#dwarf-and-dsym,iphoneos-arm64,iphoneos-armv7 \ + //Telegram:AppPackage#iphoneos-arm64,iphoneos-armv7 \ + //Telegram:Telegram#dwarf-and-dsym,iphoneos-arm64,iphoneos-armv7 \ //submodules/MtProtoKit:MtProtoKit#dwarf-and-dsym,shared,iphoneos-arm64,iphoneos-armv7 \ //submodules/MtProtoKit:MtProtoKit#shared,iphoneos-arm64,iphoneos-armv7 \ //submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#dwarf-and-dsym,shared,iphoneos-arm64,iphoneos-armv7 \ @@ -166,29 +189,29 @@ build: check_env //submodules/Display:Display#shared,iphoneos-arm64,iphoneos-armv7 \ //submodules/TelegramUI:TelegramUI#dwarf-and-dsym,shared,iphoneos-arm64,iphoneos-armv7 \ //submodules/TelegramUI:TelegramUI#shared,iphoneos-arm64,iphoneos-armv7 \ - //:WatchAppExtension#dwarf-and-dsym,watchos-arm64_32,watchos-armv7k \ - //:ShareExtension#dwarf-and-dsym,iphoneos-arm64,iphoneos-armv7 \ - //:WidgetExtension#dwarf-and-dsym,iphoneos-arm64,iphoneos-armv7 \ - //:NotificationContentExtension#dwarf-and-dsym,iphoneos-arm64,iphoneos-armv7 \ - //:NotificationServiceExtension#dwarf-and-dsym,iphoneos-arm64,iphoneos-armv7 \ - //:IntentsExtension#dwarf-and-dsym,iphoneos-arm64,iphoneos-armv7 \ + //Telegram:WatchAppExtension#dwarf-and-dsym,watchos-arm64_32,watchos-armv7k \ + //Telegram:ShareExtension#dwarf-and-dsym,iphoneos-arm64,iphoneos-armv7 \ + //Telegram:WidgetExtension#dwarf-and-dsym,iphoneos-arm64,iphoneos-armv7 \ + //Telegram:NotificationContentExtension#dwarf-and-dsym,iphoneos-arm64,iphoneos-armv7 \ + //Telegram:NotificationServiceExtension#dwarf-and-dsym,iphoneos-arm64,iphoneos-armv7 \ + //Telegram:IntentsExtension#dwarf-and-dsym,iphoneos-arm64,iphoneos-armv7 \ ${BUCK_OPTIONS} ${BUCK_RELEASE_OPTIONS} ${BUCK_THREADS_OPTIONS} ${BUCK_CACHE_OPTIONS} package_arm64: PACKAGE_DEVELOPMENT_TEAM="${DEVELOPMENT_TEAM}" \ PACKAGE_CODE_SIGN_IDENTITY="${DISTRIBUTION_CODE_SIGN_IDENTITY}" \ PACKAGE_PROVISIONING_PROFILE_APP="${DISTRIBUTION_PROVISIONING_PROFILE_APP}" \ - PACKAGE_ENTITLEMENTS_APP="${ENTITLEMENTS_APP}" \ + PACKAGE_ENTITLEMENTS_APP="Telegram/${ENTITLEMENTS_APP}" \ PACKAGE_PROVISIONING_PROFILE_EXTENSION_Share="${DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_SHARE}" \ - PACKAGE_ENTITLEMENTS_EXTENSION_Share="${ENTITLEMENTS_EXTENSION_SHARE}" \ + PACKAGE_ENTITLEMENTS_EXTENSION_Share="Telegram/${ENTITLEMENTS_EXTENSION_SHARE}" \ PACKAGE_PROVISIONING_PROFILE_EXTENSION_Widget="${DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_WIDGET}" \ - PACKAGE_ENTITLEMENTS_EXTENSION_Widget="${ENTITLEMENTS_EXTENSION_WIDGET}" \ + PACKAGE_ENTITLEMENTS_EXTENSION_Widget="Telegram/${ENTITLEMENTS_EXTENSION_WIDGET}" \ PACKAGE_PROVISIONING_PROFILE_EXTENSION_NotificationService="${DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_NOTIFICATIONSERVICE}" \ - PACKAGE_ENTITLEMENTS_EXTENSION_NotificationService="${ENTITLEMENTS_EXTENSION_NOTIFICATIONSERVICE}" \ + PACKAGE_ENTITLEMENTS_EXTENSION_NotificationService="Telegram/${ENTITLEMENTS_EXTENSION_NOTIFICATIONSERVICE}" \ PACKAGE_PROVISIONING_PROFILE_EXTENSION_NotificationContent="${DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_NOTIFICATIONCONTENT}" \ - PACKAGE_ENTITLEMENTS_EXTENSION_NotificationContent="${ENTITLEMENTS_EXTENSION_NOTIFICATIONCONTENT}" \ + PACKAGE_ENTITLEMENTS_EXTENSION_NotificationContent="Telegram/${ENTITLEMENTS_EXTENSION_NOTIFICATIONCONTENT}" \ PACKAGE_PROVISIONING_PROFILE_EXTENSION_Intents="${DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_INTENTS}" \ - PACKAGE_ENTITLEMENTS_EXTENSION_Intents="${ENTITLEMENTS_EXTENSION_INTENTS}" \ + PACKAGE_ENTITLEMENTS_EXTENSION_Intents="Telegram/${ENTITLEMENTS_EXTENSION_INTENTS}" \ PACKAGE_PROVISIONING_PROFILE_WATCH_APP="${DISTRIBUTION_PROVISIONING_PROFILE_WATCH_APP}" \ PACKAGE_PROVISIONING_PROFILE_WATCH_EXTENSION="${DISTRIBUTION_PROVISIONING_PROFILE_WATCH_EXTENSION}" \ PACKAGE_BUNDLE_ID="${BUNDLE_ID}" \ @@ -198,17 +221,17 @@ package_armv7: PACKAGE_DEVELOPMENT_TEAM="${DEVELOPMENT_TEAM}" \ PACKAGE_CODE_SIGN_IDENTITY="${DISTRIBUTION_CODE_SIGN_IDENTITY}" \ PACKAGE_PROVISIONING_PROFILE_APP="${DISTRIBUTION_PROVISIONING_PROFILE_APP}" \ - PACKAGE_ENTITLEMENTS_APP="${ENTITLEMENTS_APP}" \ + PACKAGE_ENTITLEMENTS_APP="Telegram/${ENTITLEMENTS_APP}" \ PACKAGE_PROVISIONING_PROFILE_EXTENSION_Share="${DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_SHARE}" \ - PACKAGE_ENTITLEMENTS_EXTENSION_Share="${ENTITLEMENTS_EXTENSION_SHARE}" \ + PACKAGE_ENTITLEMENTS_EXTENSION_Share="Telegram/${ENTITLEMENTS_EXTENSION_SHARE}" \ PACKAGE_PROVISIONING_PROFILE_EXTENSION_Widget="${DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_WIDGET}" \ - PACKAGE_ENTITLEMENTS_EXTENSION_Widget="${ENTITLEMENTS_EXTENSION_WIDGET}" \ + PACKAGE_ENTITLEMENTS_EXTENSION_Widget="Telegram/${ENTITLEMENTS_EXTENSION_WIDGET}" \ PACKAGE_PROVISIONING_PROFILE_EXTENSION_NotificationService="${DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_NOTIFICATIONSERVICE}" \ - PACKAGE_ENTITLEMENTS_EXTENSION_NotificationService="${ENTITLEMENTS_EXTENSION_NOTIFICATIONSERVICE}" \ + PACKAGE_ENTITLEMENTS_EXTENSION_NotificationService="Telegram/${ENTITLEMENTS_EXTENSION_NOTIFICATIONSERVICE}" \ PACKAGE_PROVISIONING_PROFILE_EXTENSION_NotificationContent="${DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_NOTIFICATIONCONTENT}" \ - PACKAGE_ENTITLEMENTS_EXTENSION_NotificationContent="${ENTITLEMENTS_EXTENSION_NOTIFICATIONCONTENT}" \ + PACKAGE_ENTITLEMENTS_EXTENSION_NotificationContent="Telegram/${ENTITLEMENTS_EXTENSION_NOTIFICATIONCONTENT}" \ PACKAGE_PROVISIONING_PROFILE_EXTENSION_Intents="${DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_INTENTS}" \ - PACKAGE_ENTITLEMENTS_EXTENSION_Intents="${ENTITLEMENTS_EXTENSION_INTENTS}" \ + PACKAGE_ENTITLEMENTS_EXTENSION_Intents="Telegram/${ENTITLEMENTS_EXTENSION_INTENTS}" \ PACKAGE_PROVISIONING_PROFILE_WATCH_APP="${DISTRIBUTION_PROVISIONING_PROFILE_WATCH_APP}" \ PACKAGE_PROVISIONING_PROFILE_WATCH_EXTENSION="${DISTRIBUTION_PROVISIONING_PROFILE_WATCH_EXTENSION}" \ PACKAGE_BUNDLE_ID="${BUNDLE_ID}" \ @@ -218,17 +241,17 @@ package_debug_arm64: PACKAGE_DEVELOPMENT_TEAM="${DEVELOPMENT_TEAM}" \ PACKAGE_CODE_SIGN_IDENTITY="${DEVELOPMENT_CODE_SIGN_IDENTITY}" \ PACKAGE_PROVISIONING_PROFILE_APP="${DEVELOPMENT_PROVISIONING_PROFILE_APP}" \ - PACKAGE_ENTITLEMENTS_APP="${ENTITLEMENTS_APP}" \ + PACKAGE_ENTITLEMENTS_APP="Telegram/${ENTITLEMENTS_APP}" \ PACKAGE_PROVISIONING_PROFILE_EXTENSION_Share="${DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_SHARE}" \ - PACKAGE_ENTITLEMENTS_EXTENSION_Share="${ENTITLEMENTS_EXTENSION_SHARE}" \ + PACKAGE_ENTITLEMENTS_EXTENSION_Share="Telegram/${ENTITLEMENTS_EXTENSION_SHARE}" \ PACKAGE_PROVISIONING_PROFILE_EXTENSION_Widget="${DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_WIDGET}" \ - PACKAGE_ENTITLEMENTS_EXTENSION_Widget="${ENTITLEMENTS_EXTENSION_WIDGET}" \ + PACKAGE_ENTITLEMENTS_EXTENSION_Widget="Telegram/${ENTITLEMENTS_EXTENSION_WIDGET}" \ PACKAGE_PROVISIONING_PROFILE_EXTENSION_NotificationService="${DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_NOTIFICATIONSERVICE}" \ - PACKAGE_ENTITLEMENTS_EXTENSION_NotificationService="${ENTITLEMENTS_EXTENSION_NOTIFICATIONSERVICE}" \ + PACKAGE_ENTITLEMENTS_EXTENSION_NotificationService="Telegram/${ENTITLEMENTS_EXTENSION_NOTIFICATIONSERVICE}" \ PACKAGE_PROVISIONING_PROFILE_EXTENSION_NotificationContent="${DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_NOTIFICATIONCONTENT}" \ - PACKAGE_ENTITLEMENTS_EXTENSION_NotificationContent="${ENTITLEMENTS_EXTENSION_NOTIFICATIONCONTENT}" \ + PACKAGE_ENTITLEMENTS_EXTENSION_NotificationContent="Telegram/${ENTITLEMENTS_EXTENSION_NOTIFICATIONCONTENT}" \ PACKAGE_PROVISIONING_PROFILE_EXTENSION_Intents="${DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_INTENTS}" \ - PACKAGE_ENTITLEMENTS_EXTENSION_Intents="${ENTITLEMENTS_EXTENSION_INTENTS}" \ + PACKAGE_ENTITLEMENTS_EXTENSION_Intents="Telegram/${ENTITLEMENTS_EXTENSION_INTENTS}" \ PACKAGE_PROVISIONING_PROFILE_WATCH_APP="${DEVELOPMENT_PROVISIONING_PROFILE_WATCH_APP}" \ PACKAGE_PROVISIONING_PROFILE_WATCH_EXTENSION="${DEVELOPMENT_PROVISIONING_PROFILE_WATCH_EXTENSION}" \ PACKAGE_BUNDLE_ID="${BUNDLE_ID}" \ @@ -240,17 +263,17 @@ package_debug_armv7: PACKAGE_DEVELOPMENT_TEAM="${DEVELOPMENT_TEAM}" \ PACKAGE_CODE_SIGN_IDENTITY="${DEVELOPMENT_CODE_SIGN_IDENTITY}" \ PACKAGE_PROVISIONING_PROFILE_APP="${DEVELOPMENT_PROVISIONING_PROFILE_APP}" \ - PACKAGE_ENTITLEMENTS_APP="${ENTITLEMENTS_APP}" \ + PACKAGE_ENTITLEMENTS_APP="Telegram/${ENTITLEMENTS_APP}" \ PACKAGE_PROVISIONING_PROFILE_EXTENSION_Share="${DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_SHARE}" \ - PACKAGE_ENTITLEMENTS_EXTENSION_Share="${ENTITLEMENTS_EXTENSION_SHARE}" \ + PACKAGE_ENTITLEMENTS_EXTENSION_Share="Telegram/${ENTITLEMENTS_EXTENSION_SHARE}" \ PACKAGE_PROVISIONING_PROFILE_EXTENSION_Widget="${DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_WIDGET}" \ - PACKAGE_ENTITLEMENTS_EXTENSION_Widget="${ENTITLEMENTS_EXTENSION_WIDGET}" \ + PACKAGE_ENTITLEMENTS_EXTENSION_Widget="Telegram/${ENTITLEMENTS_EXTENSION_WIDGET}" \ PACKAGE_PROVISIONING_PROFILE_EXTENSION_NotificationService="${DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_NOTIFICATIONSERVICE}" \ - PACKAGE_ENTITLEMENTS_EXTENSION_NotificationService="${ENTITLEMENTS_EXTENSION_NOTIFICATIONSERVICE}" \ + PACKAGE_ENTITLEMENTS_EXTENSION_NotificationService="Telegram/${ENTITLEMENTS_EXTENSION_NOTIFICATIONSERVICE}" \ PACKAGE_PROVISIONING_PROFILE_EXTENSION_NotificationContent="${DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_NOTIFICATIONCONTENT}" \ - PACKAGE_ENTITLEMENTS_EXTENSION_NotificationContent="${ENTITLEMENTS_EXTENSION_NOTIFICATIONCONTENT}" \ + PACKAGE_ENTITLEMENTS_EXTENSION_NotificationContent="Telegram/${ENTITLEMENTS_EXTENSION_NOTIFICATIONCONTENT}" \ PACKAGE_PROVISIONING_PROFILE_EXTENSION_Intents="${DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_INTENTS}" \ - PACKAGE_ENTITLEMENTS_EXTENSION_Intents="${ENTITLEMENTS_EXTENSION_INTENTS}" \ + PACKAGE_ENTITLEMENTS_EXTENSION_Intents="Telegram/${ENTITLEMENTS_EXTENSION_INTENTS}" \ PACKAGE_PROVISIONING_PROFILE_WATCH_APP="${DEVELOPMENT_PROVISIONING_PROFILE_WATCH_APP}" \ PACKAGE_PROVISIONING_PROFILE_WATCH_EXTENSION="${DEVELOPMENT_PROVISIONING_PROFILE_WATCH_EXTENSION}" \ PACKAGE_BUNDLE_ID="${BUNDLE_ID}" \ @@ -262,17 +285,17 @@ package: PACKAGE_DEVELOPMENT_TEAM="${DEVELOPMENT_TEAM}" \ PACKAGE_CODE_SIGN_IDENTITY="${DISTRIBUTION_CODE_SIGN_IDENTITY}" \ PACKAGE_PROVISIONING_PROFILE_APP="${DISTRIBUTION_PROVISIONING_PROFILE_APP}" \ - PACKAGE_ENTITLEMENTS_APP="${ENTITLEMENTS_APP}" \ + PACKAGE_ENTITLEMENTS_APP="Telegram/${ENTITLEMENTS_APP}" \ PACKAGE_PROVISIONING_PROFILE_EXTENSION_Share="${DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_SHARE}" \ - PACKAGE_ENTITLEMENTS_EXTENSION_Share="${ENTITLEMENTS_EXTENSION_SHARE}" \ + PACKAGE_ENTITLEMENTS_EXTENSION_Share="Telegram/${ENTITLEMENTS_EXTENSION_SHARE}" \ PACKAGE_PROVISIONING_PROFILE_EXTENSION_Widget="${DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_WIDGET}" \ - PACKAGE_ENTITLEMENTS_EXTENSION_Widget="${ENTITLEMENTS_EXTENSION_WIDGET}" \ + PACKAGE_ENTITLEMENTS_EXTENSION_Widget="Telegram/${ENTITLEMENTS_EXTENSION_WIDGET}" \ PACKAGE_PROVISIONING_PROFILE_EXTENSION_NotificationService="${DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_NOTIFICATIONSERVICE}" \ - PACKAGE_ENTITLEMENTS_EXTENSION_NotificationService="${ENTITLEMENTS_EXTENSION_NOTIFICATIONSERVICE}" \ + PACKAGE_ENTITLEMENTS_EXTENSION_NotificationService="Telegram/${ENTITLEMENTS_EXTENSION_NOTIFICATIONSERVICE}" \ PACKAGE_PROVISIONING_PROFILE_EXTENSION_NotificationContent="${DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_NOTIFICATIONCONTENT}" \ - PACKAGE_ENTITLEMENTS_EXTENSION_NotificationContent="${ENTITLEMENTS_EXTENSION_NOTIFICATIONCONTENT}" \ + PACKAGE_ENTITLEMENTS_EXTENSION_NotificationContent="Telegram/${ENTITLEMENTS_EXTENSION_NOTIFICATIONCONTENT}" \ PACKAGE_PROVISIONING_PROFILE_EXTENSION_Intents="${DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_INTENTS}" \ - PACKAGE_ENTITLEMENTS_EXTENSION_Intents="${ENTITLEMENTS_EXTENSION_INTENTS}" \ + PACKAGE_ENTITLEMENTS_EXTENSION_Intents="Telegram/${ENTITLEMENTS_EXTENSION_INTENTS}" \ PACKAGE_PROVISIONING_PROFILE_WATCH_APP="${DISTRIBUTION_PROVISIONING_PROFILE_WATCH_APP}" \ PACKAGE_PROVISIONING_PROFILE_WATCH_EXTENSION="${DISTRIBUTION_PROVISIONING_PROFILE_WATCH_EXTENSION}" \ PACKAGE_BUNDLE_ID="${BUNDLE_ID}" \ @@ -290,8 +313,8 @@ app_debug_armv7: build_debug_armv7 package_debug_armv7 build_buckdebug: check_env BUCK_DEBUG_MODE=1 $(BUCK) build \ - //:AppPackage#iphoneos-arm64 \ - //:Telegram#dwarf-and-dsym,iphoneos-arm64 \ + //Telegram:AppPackage#iphoneos-arm64 \ + //Telegram:Telegram#dwarf-and-dsym,iphoneos-arm64 \ //submodules/MtProtoKit:MtProtoKit#dwarf-and-dsym,shared,iphoneos-arm64 \ //submodules/MtProtoKit:MtProtoKit#shared,iphoneos-arm64 \ //submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#dwarf-and-dsym,shared,iphoneos-arm64 \ @@ -310,28 +333,18 @@ build_buckdebug: check_env //submodules/Display:Display#shared,iphoneos-arm64 \ //submodules/TelegramUI:TelegramUI#dwarf-and-dsym,shared,iphoneos-arm64 \ //submodules/TelegramUI:TelegramUI#shared,iphoneos-arm64 \ - //:WatchAppExtension#dwarf-and-dsym,watchos-arm64_32,watchos-armv7k \ - //:ShareExtension#dwarf-and-dsym,iphoneos-arm64 \ - //:WidgetExtension#dwarf-and-dsym,iphoneos-arm64 \ - //:NotificationContentExtension#dwarf-and-dsym,iphoneos-arm64 \ - //:NotificationServiceExtension#dwarf-and-dsym,iphoneos-arm64 \ - //:IntentsExtension#dwarf-and-dsym,iphoneos-arm64 \ + //Telegram:WatchAppExtension#dwarf-and-dsym,watchos-arm64_32,watchos-armv7k \ + //Telegram:ShareExtension#dwarf-and-dsym,iphoneos-arm64 \ + //Telegram:WidgetExtension#dwarf-and-dsym,iphoneos-arm64 \ + //Telegram:NotificationContentExtension#dwarf-and-dsym,iphoneos-arm64 \ + //Telegram:NotificationServiceExtension#dwarf-and-dsym,iphoneos-arm64 \ + //Telegram:IntentsExtension#dwarf-and-dsym,iphoneos-arm64 \ --verbose 7 ${BUCK_OPTIONS} ${BUCK_DEBUG_OPTIONS} -build_buckdebug_one: check_env - BUCK_DEBUG_MODE=1 $(BUCK) build \ - //submodules/Postbox:Postbox#shared,iphoneos-arm64 \ - --verbose 7 ${BUCK_OPTIONS} ${BUCK_DEBUG_OPTIONS} - -build_verbose_one: check_env - $(BUCK) build \ - //submodules/Postbox:Postbox#shared,iphoneos-arm64 \ - --verbose 7 ${BUCK_OPTIONS} ${BUCK_DEBUG_OPTIONS} - build_verbose: check_env $(BUCK) build \ - //:AppPackage#iphoneos-arm64 \ - //:Telegram#dwarf-and-dsym,iphoneos-arm64 \ + //Telegram:AppPackage#iphoneos-arm64 \ + //Telegram:Telegram#dwarf-and-dsym,iphoneos-arm64 \ //submodules/MtProtoKit:MtProtoKit#dwarf-and-dsym,shared,iphoneos-arm64 \ //submodules/MtProtoKit:MtProtoKit#shared,iphoneos-arm64 \ //submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#dwarf-and-dsym,shared,iphoneos-arm64 \ @@ -350,48 +363,66 @@ build_verbose: check_env //submodules/Display:Display#shared,iphoneos-arm64 \ //submodules/TelegramUI:TelegramUI#dwarf-and-dsym,shared,iphoneos-arm64 \ //submodules/TelegramUI:TelegramUI#shared,iphoneos-arm64 \ - //:WatchAppExtension#dwarf-and-dsym,watchos-arm64_32,watchos-armv7k \ - //:ShareExtension#dwarf-and-dsym,iphoneos-arm64 \ - //:WidgetExtension#dwarf-and-dsym,iphoneos-arm64 \ - //:NotificationContentExtension#dwarf-and-dsym,iphoneos-arm64 \ - //:NotificationServiceExtension#dwarf-and-dsym,iphoneos-arm64 \ - //:IntentsExtension#dwarf-and-dsym,iphoneos-arm64 \ + //Telegram:WatchAppExtension#dwarf-and-dsym,watchos-arm64_32,watchos-armv7k \ + //Telegram:ShareExtension#dwarf-and-dsym,iphoneos-arm64 \ + //Telegram:WidgetExtension#dwarf-and-dsym,iphoneos-arm64 \ + //Telegram:NotificationContentExtension#dwarf-and-dsym,iphoneos-arm64 \ + //Telegram:NotificationServiceExtension#dwarf-and-dsym,iphoneos-arm64 \ + //Telegram:IntentsExtension#dwarf-and-dsym,iphoneos-arm64 \ --verbose 7 ${BUCK_OPTIONS} ${BUCK_THREADS_OPTIONS} ${BUCK_DEBUG_OPTIONS} ${BUCK_CACHE_OPTIONS} deps: check_env - $(BUCK) query "deps(//:AppPackage)" --dot \ + $(BUCK) query "deps(//Telegram:AppPackage)" --dot \ ${BUCK_OPTIONS} ${BUCK_DEBUG_OPTIONS} -build_openssl: check_env - $(BUCK) build \ - //submodules/openssl:openssl#iphoneos-arm64 \ - --verbose 7 ${BUCK_OPTIONS} ${BUCK_THREADS_OPTIONS} ${BUCK_DEBUG_OPTIONS} - -build_libphonenumber: check_env - $(BUCK) build \ - //submodules/libphonenumber:libphonenumber#iphoneos-arm64 \ - ${BUCK_OPTIONS} ${BUCK_THREADS_OPTIONS} ${BUCK_DEBUG_OPTIONS} - -build_ton: check_env - $(BUCK) build \ - //submodules/ton:ton#iphoneos-arm64 \ - --verbose 7 ${BUCK_OPTIONS} ${BUCK_THREADS_OPTIONS} ${BUCK_DEBUG_OPTIONS} - clean: kill_xcode sh clean.sh project: check_env kill_xcode - $(BUCK) project //:workspace --config custom.mode=project ${BUCK_OPTIONS} ${BUCK_DEBUG_OPTIONS} - open Telegram_Buck.xcworkspace + $(BUCK) project //Telegram:workspace --config custom.mode=project ${BUCK_OPTIONS} ${BUCK_DEBUG_OPTIONS} + open Telegram/Telegram_Buck.xcworkspace -project_opt: check_env kill_xcode - $(BUCK) project //:workspace --config custom.mode=project ${BUCK_OPTIONS} ${BUCK_RELEASE_OPTIONS} - open Telegram_Buck.xcworkspace +bazel_app_debug_arm64: + APP_VERSION="${APP_VERSION}" \ + build-system/prepare-build.sh distribution + "${BAZEL}" build Telegram/Telegram ${BAZEL_CACHE_FLAGS} ${BAZEL_COMMON_FLAGS} ${BAZEL_DEBUG_FLAGS} \ + -c dbg \ + --ios_multi_cpus=arm64 \ + --watchos_cpus=armv7k,arm64_32 \ + --verbose_failures -project_buckdebug: check_env kill_xcode - BUCK_DEBUG_MODE=1 $(BUCK) project //:workspace --config custom.mode=project ${BUCK_OPTIONS} ${BUCK_DEBUG_OPTIONS} - open Telegram_Buck.xcworkspace +bazel_app_arm64: + APP_VERSION="${APP_VERSION}" \ + BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \ + build-system/prepare-build.sh distribution + "${BAZEL}" build Telegram/Telegram ${BAZEL_CACHE_FLAGS} ${BAZEL_COMMON_FLAGS} ${BAZEL_OPT_FLAGS} \ + -c opt \ + --ios_multi_cpus=arm64 \ + --watchos_cpus=armv7k,arm64_32 \ + --objc_enable_binary_stripping=true \ + --features=dead_strip \ + --apple_generate_dsym \ + --output_groups=+dsyms \ + --verbose_failures + +bazel_prepare_development_build: + APP_VERSION="${APP_VERSION}" \ + BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \ + build-system/prepare-build.sh development + +bazel_project: kill_xcode bazel_prepare_development_build + APP_VERSION="${APP_VERSION}" \ + BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \ + build-system/generate-xcode-project.sh + +bazel_soft_project: bazel_prepare_development_build + APP_VERSION="${APP_VERSION}" \ + BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \ + build-system/generate-xcode-project.sh + +bazel_opt_project: bazel_prepare_development_build + APP_VERSION="${APP_VERSION}" \ + BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \ + GENERATE_OPT_PROJECT=1 \ + build-system/generate-xcode-project.sh -temp_project: check_env kill_xcode - $(BUCK) project //Temp:workspace --config custom.mode=project ${BUCK_OPTIONS} ${BUCK_DEBUG_OPTIONS} - open Temp/Telegram_Buck.xcworkspace diff --git a/Telegram-iOS UITests/Images/Bitmap1.png b/Telegram-iOS UITests/Images/Bitmap1.png deleted file mode 100644 index f0dbe85f17..0000000000 Binary files a/Telegram-iOS UITests/Images/Bitmap1.png and /dev/null differ diff --git a/Telegram-iOS UITests/Images/Bitmap10.png b/Telegram-iOS UITests/Images/Bitmap10.png deleted file mode 100644 index ac46a069b7..0000000000 Binary files a/Telegram-iOS UITests/Images/Bitmap10.png and /dev/null differ diff --git a/Telegram-iOS UITests/Images/Bitmap11.png b/Telegram-iOS UITests/Images/Bitmap11.png deleted file mode 100644 index d64c41bae2..0000000000 Binary files a/Telegram-iOS UITests/Images/Bitmap11.png and /dev/null differ diff --git a/Telegram-iOS UITests/Images/Bitmap12.png b/Telegram-iOS UITests/Images/Bitmap12.png deleted file mode 100644 index 2f450cf3eb..0000000000 Binary files a/Telegram-iOS UITests/Images/Bitmap12.png and /dev/null differ diff --git a/Telegram-iOS UITests/Images/Bitmap2.png b/Telegram-iOS UITests/Images/Bitmap2.png deleted file mode 100644 index 149b4d88e5..0000000000 Binary files a/Telegram-iOS UITests/Images/Bitmap2.png and /dev/null differ diff --git a/Telegram-iOS UITests/Images/Bitmap3.png b/Telegram-iOS UITests/Images/Bitmap3.png deleted file mode 100644 index 66b3bef10f..0000000000 Binary files a/Telegram-iOS UITests/Images/Bitmap3.png and /dev/null differ diff --git a/Telegram-iOS UITests/Images/Bitmap5.png b/Telegram-iOS UITests/Images/Bitmap5.png deleted file mode 100644 index a053de0159..0000000000 Binary files a/Telegram-iOS UITests/Images/Bitmap5.png and /dev/null differ diff --git a/Telegram-iOS UITests/Images/Bitmap6.png b/Telegram-iOS UITests/Images/Bitmap6.png deleted file mode 100644 index 7f3854f431..0000000000 Binary files a/Telegram-iOS UITests/Images/Bitmap6.png and /dev/null differ diff --git a/Telegram-iOS UITests/Images/Bitmap7.png b/Telegram-iOS UITests/Images/Bitmap7.png deleted file mode 100644 index 346939bbd7..0000000000 Binary files a/Telegram-iOS UITests/Images/Bitmap7.png and /dev/null differ diff --git a/Telegram-iOS UITests/Images/Bitmap8.png b/Telegram-iOS UITests/Images/Bitmap8.png deleted file mode 100644 index 7a32f43fdb..0000000000 Binary files a/Telegram-iOS UITests/Images/Bitmap8.png and /dev/null differ diff --git a/Telegram-iOS UITests/Images/Bitmap9.png b/Telegram-iOS UITests/Images/Bitmap9.png deleted file mode 100644 index 07fa8d4aa5..0000000000 Binary files a/Telegram-iOS UITests/Images/Bitmap9.png and /dev/null differ diff --git a/Telegram-iOS UITests/Info.plist b/Telegram-iOS UITests/Info.plist deleted file mode 100644 index 469d7cc131..0000000000 --- a/Telegram-iOS UITests/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.0 - CFBundleVersion - 667 - - diff --git a/Telegram-iOS UITests/SnapshotHelper.swift b/Telegram-iOS UITests/SnapshotHelper.swift deleted file mode 100644 index 3ba8f8cdfb..0000000000 --- a/Telegram-iOS UITests/SnapshotHelper.swift +++ /dev/null @@ -1,196 +0,0 @@ -// -// SnapshotHelper.swift -// Example -// -// Created by Felix Krause on 10/8/15. -// Copyright © 2015 Felix Krause. All rights reserved. -// - -// ----------------------------------------------------- -// IMPORTANT: When modifying this file, make sure to -// increment the version number at the very -// bottom of the file to notify users about -// the new SnapshotHelper.swift -// ----------------------------------------------------- - -import Foundation -import XCTest - -var deviceLanguage = "" -var locale = "" - -@available(*, deprecated, message: "use setupSnapshot: instead") -func setLanguage(_ app: XCUIApplication) { - setupSnapshot(app) -} - -func setupSnapshot(_ app: XCUIApplication) { - Snapshot.setupSnapshot(app) -} - -func snapshot(_ name: String, waitForLoadingIndicator: Bool = true) { - Snapshot.snapshot(name, waitForLoadingIndicator: waitForLoadingIndicator) -} - -enum SnapshotError: Error, CustomDebugStringConvertible { - case cannotDetectUser - case cannotFindHomeDirectory - case cannotFindSimulatorHomeDirectory - case cannotAccessSimulatorHomeDirectory(String) - - var debugDescription: String { - switch self { - case .cannotDetectUser: - return "Couldn't find Snapshot configuration files - can't detect current user " - case .cannotFindHomeDirectory: - return "Couldn't find Snapshot configuration files - can't detect `Users` dir" - case .cannotFindSimulatorHomeDirectory: - return "Couldn't find simulator home location. Please, check SIMULATOR_HOST_HOME env variable." - case .cannotAccessSimulatorHomeDirectory(let simulatorHostHome): - return "Can't prepare environment. Simulator home location is inaccessible. Does \(simulatorHostHome) exist?" - } - } -} - -open class Snapshot: NSObject { - static var app: XCUIApplication! - static var cacheDirectory: URL! - static var screenshotsDirectory: URL? { - return cacheDirectory.appendingPathComponent("screenshots", isDirectory: true) - } - - open class func setupSnapshot(_ app: XCUIApplication) { - do { - let cacheDir = try pathPrefix() - Snapshot.cacheDirectory = cacheDir - print("cacheDir \(cacheDir)") - Snapshot.app = app - setLanguage(app) - setLocale(app) - setLaunchArguments(app) - } catch let error { - print(error) - } - } - - class func setLanguage(_ app: XCUIApplication) { - let path = cacheDirectory.appendingPathComponent("language.txt") - - do { - let trimCharacterSet = CharacterSet.whitespacesAndNewlines - deviceLanguage = try String(contentsOf: path, encoding: .utf8).trimmingCharacters(in: trimCharacterSet) - app.launchArguments += ["-AppleLanguages", "(\(deviceLanguage))"] - } catch { - print("Couldn't detect/set language...") - } - } - - class func setLocale(_ app: XCUIApplication) { - let path = cacheDirectory.appendingPathComponent("locale.txt") - - do { - let trimCharacterSet = CharacterSet.whitespacesAndNewlines - locale = try String(contentsOf: path, encoding: .utf8).trimmingCharacters(in: trimCharacterSet) - } catch { - print("Couldn't detect/set locale...") - } - if locale.isEmpty { - locale = Locale(identifier: deviceLanguage).identifier - } - app.launchArguments += ["-AppleLocale", "\"\(locale)\""] - } - - class func setLaunchArguments(_ app: XCUIApplication) { - let path = cacheDirectory.appendingPathComponent("snapshot-launch_arguments.txt") - app.launchArguments += ["-FASTLANE_SNAPSHOT", "YES", "-ui_testing"] - - do { - let launchArguments = try String(contentsOf: path, encoding: String.Encoding.utf8) - let regex = try NSRegularExpression(pattern: "(\\\".+?\\\"|\\S+)", options: []) - let matches = regex.matches(in: launchArguments, options: [], range: NSRange(location:0, length: launchArguments.count)) - let results = matches.map { result -> String in - (launchArguments as NSString).substring(with: result.range) - } - app.launchArguments += results - } catch { - print("Couldn't detect/set launch_arguments...") - } - } - - open class func snapshot(_ name: String, waitForLoadingIndicator: Bool = true) { - if waitForLoadingIndicator { - waitForLoadingIndicatorToDisappear() - } - - print("snapshot: \(name)") // more information about this, check out https://github.com/fastlane/fastlane/tree/master/snapshot#how-does-it-work - - sleep(1) // Waiting for the animation to be finished (kind of) - - #if os(OSX) - XCUIApplication().typeKey(XCUIKeyboardKeySecondaryFn, modifierFlags: []) - #else - let screenshot = app.windows.firstMatch.screenshot() - guard let simulator = ProcessInfo().environment["SIMULATOR_DEVICE_NAME"], let screenshotsDir = screenshotsDirectory else { return } - let path = screenshotsDir.appendingPathComponent("\(simulator)-\(name).png") - do { - try screenshot.pngRepresentation.write(to: path) - } catch let error { - print("Problem writing screenshot: \(name) to \(path)") - print(error) - } - #endif - } - - class func waitForLoadingIndicatorToDisappear() { - #if os(tvOS) - return - #endif - - let query = XCUIApplication().statusBars.children(matching: .other).element(boundBy: 1).children(matching: .other) - - while (0.. URL? { - let homeDir: URL - // on OSX config is stored in /Users//Library - // and on iOS/tvOS/WatchOS it's in simulator's home dir - #if os(OSX) - guard let user = ProcessInfo().environment["USER"] else { - throw SnapshotError.cannotDetectUser - } - - guard let usersDir = FileManager.default.urls(for: .userDirectory, in: .localDomainMask).first else { - throw SnapshotError.cannotFindHomeDirectory - } - - homeDir = usersDir.appendingPathComponent(user) - #else - guard let simulatorHostHome = ProcessInfo().environment["SIMULATOR_HOST_HOME"] else { - throw SnapshotError.cannotFindSimulatorHomeDirectory - } - guard let homeDirUrl = URL(string: simulatorHostHome) else { - throw SnapshotError.cannotAccessSimulatorHomeDirectory(simulatorHostHome) - } - homeDir = URL(fileURLWithPath: homeDirUrl.path) - #endif - return homeDir.appendingPathComponent("Library/Caches/tools.fastlane") - } -} - -extension XCUIElement { - var isLoadingIndicator: Bool { - let whiteListedLoaders = ["GeofenceLocationTrackingOn", "StandardLocationTrackingOn"] - if whiteListedLoaders.contains(self.identifier) { - return false - } - return self.frame.size == CGSize(width: 10, height: 20) - } -} - -// Please don't remove the lines below -// They are used to detect outdated configuration files -// SnapshotHelperVersion [1.5] diff --git a/Telegram-iOS UITests/Telegram_iOS_UITests.swift b/Telegram-iOS UITests/Telegram_iOS_UITests.swift deleted file mode 100644 index 77877422c0..0000000000 --- a/Telegram-iOS UITests/Telegram_iOS_UITests.swift +++ /dev/null @@ -1,53 +0,0 @@ -import XCTest - -class Telegram_iOS_UITests: XCTestCase { - var app: XCUIApplication! - - override func setUp() { - super.setUp() - - self.continueAfterFailure = false - - self.app = XCUIApplication() - let path = Bundle(for: type(of: self)).bundlePath - - self.app.launchEnvironment["snapshot-data-path"] = path - setupSnapshot(app) - } - - override func tearDown() { - super.tearDown() - } - - func testChatList() { - self.app.launchArguments = ["snapshot:chat-list"] - self.app.launch() - XCTAssert(self.app.wait(for: .runningForeground, timeout: 10.0)) - snapshot("01ChatList") - sleep(1) - } - - func testSecretChat() { - self.app.launchArguments = ["snapshot:secret-chat"] - self.app.launch() - XCTAssert(self.app.wait(for: .runningForeground, timeout: 10.0)) - snapshot("02SecretChat") - sleep(1) - } - - func testSettings() { - self.app.launchArguments = ["snapshot:settings"] - self.app.launch() - XCTAssert(self.app.wait(for: .runningForeground, timeout: 10.0)) - snapshot("04Settings") - sleep(1) - } - - func testAppearanceSettings() { - self.app.launchArguments = ["snapshot:appearance-settings"] - self.app.launch() - XCTAssert(self.app.wait(for: .runningForeground, timeout: 10.0)) - snapshot("05AppearanceSettings") - sleep(1) - } -} diff --git a/Telegram-iOSTests/Info.plist b/Telegram-iOSTests/Info.plist deleted file mode 100644 index 4e002d9359..0000000000 --- a/Telegram-iOSTests/Info.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 667 - - diff --git a/Telegram-iOSTests/ListViewTests.swift b/Telegram-iOSTests/ListViewTests.swift deleted file mode 100644 index c5da8cd790..0000000000 --- a/Telegram-iOSTests/ListViewTests.swift +++ /dev/null @@ -1,29 +0,0 @@ -import XCTest -import Display - -class ListViewTests: XCTestCase { - var listView: ListView! - - override func setUp() { - super.setUp() - - // Put setup code here. This method is called before the invocation of each test method in the class. - - // In UI tests it is usually best to stop immediately when a failure occurs. - continueAfterFailure = false - // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. - XCUIApplication().launch() - - // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() - } - - func testExample() { - // Use recording to get started writing UI tests. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } -} diff --git a/Telegram-iOSTests/Telegram_iOSTests.swift b/Telegram-iOSTests/Telegram_iOSTests.swift deleted file mode 100644 index eb6bb07928..0000000000 --- a/Telegram-iOSTests/Telegram_iOSTests.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// Telegram_iOSTests.swift -// Telegram-iOSTests -// -// Created by Peter on 10/06/15. -// Copyright (c) 2015 Telegram. All rights reserved. -// - -import UIKit -import XCTest - -class Telegram_iOSTests: XCTestCase { - - override func setUp() { - super.setUp() - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() - } - - func testExample() { - // This is an example of a functional test case. - XCTAssert(true, "Pass") - } - - func testPerformanceExample() { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } - } - -} diff --git a/Telegram/BUCK b/Telegram/BUCK new file mode 100644 index 0000000000..ff2f315899 --- /dev/null +++ b/Telegram/BUCK @@ -0,0 +1,551 @@ +load("//Config:utils.bzl", + "library_configs", +) + +load("//Config:configs.bzl", + "app_binary_configs", + "share_extension_configs", + "widget_extension_configs", + "notification_content_extension_configs", + "notification_service_extension_configs", + "intents_extension_configs", + "watch_extension_binary_configs", + "watch_binary_configs", + "info_plist_substitutions", + "app_info_plist_substitutions", + "share_extension_info_plist_substitutions", + "widget_extension_info_plist_substitutions", + "notification_content_extension_info_plist_substitutions", + "notification_service_extension_info_plist_substitutions", + "intents_extension_info_plist_substitutions", + "watch_extension_info_plist_substitutions", + "watch_info_plist_substitutions", + "DEVELOPMENT_LANGUAGE", +) + +load("//Config:buck_rule_macros.bzl", + "apple_lib", + "framework_binary_dependencies", + "framework_bundle_dependencies", + "glob_map", + "glob_sub_map", + "merge_maps", +) + +framework_dependencies = [ + "//submodules/MtProtoKit:MtProtoKit", + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/Postbox:Postbox", + "//submodules/TelegramApi:TelegramApi", + "//submodules/SyncCore:SyncCore", + "//submodules/TelegramCore:TelegramCore", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/TelegramUI:TelegramUI", +] + +resource_dependencies = [ + "//submodules/LegacyComponents:LegacyComponentsResources", + "//submodules/TelegramUI:TelegramUIAssets", + "//submodules/TelegramUI:TelegramUIResources", + #"//submodules/WalletUI:WalletUIAssets", + #"//submodules/WalletUI:WalletUIResources", + "//submodules/PasswordSetupUI:PasswordSetupUIResources", + "//submodules/PasswordSetupUI:PasswordSetupUIAssets", + "//submodules/OverlayStatusController:OverlayStatusControllerResources", + ":AppResources", + ":AppStringResources", + ":InfoPlistStringResources", + ":AppIntentVocabularyResources", + ":Icons", + ":AdditionalIcons", + ":LaunchScreen", +] + +build_phase_scripts = [ +] + +apple_resource( + name = "AppResources", + files = glob([ + "Telegram-iOS/Resources/**/*", + ], exclude = ["Telegram-iOS/Resources/**/.*"]), + visibility = ["PUBLIC"], +) + +apple_resource( + name = "AppStringResources", + files = [], + variants = glob([ + "Telegram-iOS/*.lproj/Localizable.strings", + ]), + visibility = ["PUBLIC"], +) + +apple_resource( + name = "AppIntentVocabularyResources", + files = [], + variants = glob([ + "Telegram-iOS/*.lproj/AppIntentVocabulary.plist", + ]), + visibility = ["PUBLIC"], +) + +apple_resource( + name = "InfoPlistStringResources", + files = [], + variants = glob([ + "Telegram-iOS/*.lproj/InfoPlist.strings", + ]), + visibility = ["PUBLIC"], +) + +apple_asset_catalog( + name = "Icons", + dirs = [ + "Telegram-iOS/Icons.xcassets", + "Telegram-iOS/AppIcons.xcassets", + ], + app_icon = "AppIconLLC", + visibility = ["PUBLIC"], +) + +apple_resource( + name = "AdditionalIcons", + files = glob([ + "Telegram-iOS/*.png", + ]), + visibility = ["PUBLIC"], +) + +apple_resource( + name = "LaunchScreen", + files = [ + "Telegram-iOS/Base.lproj/LaunchScreen.xib", + ], + visibility = ["PUBLIC"], +) + +apple_library( + name = "AppLibrary", + visibility = [ + "//...", + ], + configs = library_configs(), + swift_version = native.read_config("swift", "version"), + srcs = [ + "Telegram-iOS/main.m", + "Telegram-iOS/Application.swift" + ], + deps = [ + ] + + framework_binary_dependencies(framework_dependencies), +) + +apple_binary( + name = "AppBinary", + visibility = [ + "//...", + ], + configs = app_binary_configs(), + swift_version = native.read_config("swift", "version"), + srcs = [ + "SupportFiles/Empty.swift", + ], + deps = [ + ":AppLibrary", + ] + + resource_dependencies, +) + +apple_bundle( + name = "Telegram", + visibility = [ + "//Telegram:...", + ], + extension = "app", + binary = ":AppBinary", + product_name = "Telegram", + info_plist = "Telegram-iOS/Info.plist", + info_plist_substitutions = app_info_plist_substitutions(), + deps = [ + ":ShareExtension", + ":WidgetExtension", + ":NotificationContentExtension", + ":NotificationServiceExtension", + ":IntentsExtension", + ":WatchApp#watch", + ] + + framework_bundle_dependencies(framework_dependencies), +) + +# Share Extension + +apple_binary( + name = "ShareBinary", + srcs = glob([ + "Share/**/*.swift", + ]), + configs = share_extension_configs(), + linker_flags = [ + "-e", + "_NSExtensionMain", + "-Xlinker", + "-rpath", + "-Xlinker", + "/usr/lib/swift", + "-Xlinker", + "-rpath", + "-Xlinker", + "@executable_path/../../Frameworks", + ], + deps = [ + "//submodules/TelegramUI:TelegramUI#shared", + ], + frameworks = [ + "$SDKROOT/System/Library/Frameworks/UIKit.framework", + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + ], +) + +apple_bundle( + name = "ShareExtension", + binary = ":ShareBinary", + extension = "appex", + info_plist = "Share/Info.plist", + info_plist_substitutions = share_extension_info_plist_substitutions(), + deps = [ + ], + xcode_product_type = "com.apple.product-type.app-extension", +) + +# Widget + +apple_binary( + name = "WidgetBinary", + srcs = glob([ + "Widget/**/*.swift", + ]), + configs = widget_extension_configs(), + swift_compiler_flags = [ + "-application-extension", + ], + linker_flags = [ + "-e", + "_NSExtensionMain", + "-Xlinker", + "-rpath", + "-Xlinker", + "/usr/lib/swift", + "-Xlinker", + "-rpath", + "-Xlinker", + "@executable_path/../../Frameworks", + ], + deps = [ + "//submodules/BuildConfig:BuildConfig", + "//submodules/WidgetItems:WidgetItems", + "//submodules/AppLockState:AppLockState", + ], + frameworks = [ + "$SDKROOT/System/Library/Frameworks/UIKit.framework", + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + "$SDKROOT/System/Library/Frameworks/NotificationCenter.framework", + ], +) + +apple_bundle( + name = "WidgetExtension", + binary = ":WidgetBinary", + extension = "appex", + info_plist = "Widget/Info.plist", + info_plist_substitutions = widget_extension_info_plist_substitutions(), + deps = [ + ], + xcode_product_type = "com.apple.product-type.app-extension", +) + +# Notification Content + +apple_binary( + name = "NotificationContentBinary", + srcs = glob([ + "NotificationContent/**/*.swift", + ]), + configs = notification_content_extension_configs(), + swift_compiler_flags = [ + "-application-extension", + ], + linker_flags = [ + "-e", + "_NSExtensionMain", + "-Xlinker", + "-rpath", + "-Xlinker", + "/usr/lib/swift", + "-Xlinker", + "-rpath", + "-Xlinker", + "@executable_path/../../Frameworks", + ], + deps = [ + "//submodules/TelegramUI:TelegramUI#shared", + ], + frameworks = [ + "$SDKROOT/System/Library/Frameworks/UIKit.framework", + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + "$SDKROOT/System/Library/Frameworks/UserNotificationsUI.framework", + ], +) + +apple_bundle( + name = "NotificationContentExtension", + binary = ":NotificationContentBinary", + extension = "appex", + info_plist = "NotificationContent/Info.plist", + info_plist_substitutions = notification_content_extension_info_plist_substitutions(), + deps = [ + ], + xcode_product_type = "com.apple.product-type.app-extension", +) + +#Notification Service + +apple_binary( + name = "NotificationServiceBinary", + srcs = glob([ + "NotificationService/**/*.m", + "NotificationService/**/*.swift", + ]), + headers = glob([ + "NotificationService/**/*.h", + ]), + bridging_header = "NotificationService/NotificationService-Bridging-Header.h", + configs = notification_service_extension_configs(), + swift_compiler_flags = [ + "-application-extension", + ], + linker_flags = [ + "-e", + "_NSExtensionMain", + "-Xlinker", + "-rpath", + "-Xlinker", + "/usr/lib/swift", + "-Xlinker", + "-rpath", + "-Xlinker", + "@executable_path/../../Frameworks", + ], + deps = [ + "//submodules/BuildConfig:BuildConfig", + "//submodules/MtProtoKit:MtProtoKit#shared", + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared", + "//submodules/EncryptionProvider:EncryptionProvider", + "//submodules/Database/ValueBox:ValueBox", + "//submodules/Database/PostboxDataTypes:PostboxDataTypes", + "//submodules/Database/MessageHistoryReadStateTable:MessageHistoryReadStateTable", + "//submodules/Database/MessageHistoryMetadataTable:MessageHistoryMetadataTable", + "//submodules/Database/PreferencesTable:PreferencesTable", + "//submodules/Database/PeerTable:PeerTable", + "//submodules/sqlcipher:sqlcipher", + "//submodules/AppLockState:AppLockState", + "//submodules/NotificationsPresentationData:NotificationsPresentationData", + ], + frameworks = [ + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + "$SDKROOT/System/Library/Frameworks/UserNotifications.framework", + ], +) + +apple_bundle( + name = "NotificationServiceExtension", + binary = ":NotificationServiceBinary", + extension = "appex", + info_plist = "NotificationService/Info.plist", + info_plist_substitutions = notification_service_extension_info_plist_substitutions(), + deps = [ + ], + xcode_product_type = "com.apple.product-type.app-extension", +) + +# Intents + +apple_binary( + name = "IntentsBinary", + srcs = glob([ + "SiriIntents/**/*.swift", + ]), + configs = intents_extension_configs(), + swift_compiler_flags = [ + "-application-extension", + ], + linker_flags = [ + "-e", + "_NSExtensionMain", + "-Xlinker", + "-rpath", + "-Xlinker", + "/usr/lib/swift", + "-Xlinker", + "-rpath", + "-Xlinker", + "@executable_path/../../Frameworks", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared", + "//submodules/Postbox:Postbox#shared", + "//submodules/TelegramApi:TelegramApi#shared", + "//submodules/SyncCore:SyncCore#shared", + "//submodules/TelegramCore:TelegramCore#shared", + "//submodules/BuildConfig:BuildConfig", + "//submodules/OpenSSLEncryptionProvider:OpenSSLEncryptionProvider", + "//submodules/AppLockState:AppLockState", + ], + frameworks = [ + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + "$SDKROOT/System/Library/Frameworks/Intents.framework", + "$SDKROOT/System/Library/Frameworks/Contacts.framework", + ], +) + +apple_bundle( + name = "IntentsExtension", + binary = ":IntentsBinary", + extension = "appex", + info_plist = "SiriIntents/Info.plist", + info_plist_substitutions = intents_extension_info_plist_substitutions(), + deps = [ + ], + xcode_product_type = "com.apple.product-type.app-extension", +) + +# Watch + +apple_resource( + name = "WatchAppStringResources", + files = [], + variants = glob([ + "Telegram-iOS/*.lproj/Localizable.strings", + ]), + visibility = ["PUBLIC"], +) + +apple_resource( + name = "WatchAppExtensionResources", + files = glob([ + "Watch/Extension/Resources/**/*", + ], exclude = ["Watch/Extension/Resources/**/.*"]), + visibility = ["PUBLIC"], +) + +apple_binary( + name = "WatchAppExtensionBinary", + srcs = glob([ + "Watch/Extension/**/*.m", + "Watch/SSignalKit/**/*.m", + "Watch/Bridge/**/*.m", + "Watch/WatchCommonWatch/**/*.m", + ]), + headers = merge_maps([ + glob_map(glob([ + "Watch/Extension/*.h", + "Watch/Bridge/*.h", + ])), + glob_sub_map("Watch/Extension/", glob([ + "Watch/Extension/SSignalKit/*.h", + ])), + glob_sub_map("Watch/", glob([ + "Watch/WatchCommonWatch/*.h", + ])), + ]), + compiler_flags = [ + "-DTARGET_OS_WATCH=1", + ], + linker_flags = [ + "-e", + "_WKExtensionMain", + "-lWKExtensionMainLegacy", + ], + configs = watch_extension_binary_configs(), + frameworks = [ + "$SDKROOT/System/Library/Frameworks/UserNotifications.framework", + "$SDKROOT/System/Library/Frameworks/CoreLocation.framework", + "$SDKROOT/System/Library/Frameworks/CoreGraphics.framework", + ], + deps = [ + ":WatchAppStringResources", + ":WatchAppExtensionResources", + ], +) + +apple_bundle( + name = "WatchAppExtension", + binary = ":WatchAppExtensionBinary", + extension = "appex", + info_plist = "Watch/Extension/Info.plist", + info_plist_substitutions = watch_extension_info_plist_substitutions(), + xcode_product_type = "com.apple.product-type.watchkit2-extension", +) + +apple_resource( + name = "WatchAppResources", + dirs = [], + files = glob(["Watch/Extension/Resources/*.png"]) +) + +apple_asset_catalog( + name = "WatchAppAssets", + dirs = [ + "Watch/App/Assets.xcassets", + ], + app_icon = "AppIcon", + visibility = ["PUBLIC"], +) + +apple_resource( + name = "WatchAppInterface", + files = [ + "Watch/App/Base.lproj/Interface.storyboard", + ], + visibility = ["PUBLIC"], +) + +apple_binary( + name = "WatchAppBinary", + configs = watch_binary_configs(), + deps = [ + ":WatchAppResources", + ":WatchAppAssets", + ":WatchAppInterface", + ":WatchAppStringResources", + ], +) + +apple_bundle( + name = "WatchApp", + binary = ":WatchAppBinary", + visibility = [ + "//Telegram:...", + ], + extension = "app", + info_plist = "Watch/App/Info.plist", + info_plist_substitutions = watch_info_plist_substitutions(), + xcode_product_type = "com.apple.product-type.application.watchapp2", + deps = [ + ":WatchAppExtension", + ], +) + +# Package + +apple_package( + name = "AppPackage", + bundle = ":Telegram", +) + +xcode_workspace_config( + name = "workspace", + workspace_name = "Telegram_Buck", + src_target = ":Telegram", +) diff --git a/Telegram/BUILD b/Telegram/BUILD new file mode 100644 index 0000000000..d438aecce4 --- /dev/null +++ b/Telegram/BUILD @@ -0,0 +1,822 @@ +load("@build_bazel_rules_apple//apple:ios.bzl", + "ios_application", + "ios_extension", + "ios_framework", +) + +load("@build_bazel_rules_apple//apple:watchos.bzl", + "watchos_application", + "watchos_extension", +) + +load("@build_bazel_rules_apple//apple:versioning.bzl", + "apple_bundle_version", +) + +load("@build_bazel_rules_swift//swift:swift.bzl", + "swift_library", +) + +load("//build-system:plist_fragment.bzl", + "plist_fragment", +) + +load( + "//build-input/data:variables.bzl", + "telegram_build_number", + "telegram_version", + "telegram_bundle_id", + "telegram_aps_environment", + "telegram_team_id", +) + +config_setting( + name = "debug", + values = { + "compilation_mode": "dbg", + }, +) + +genrule( + name = "empty", + outs = ["empty.swift"], + cmd = "touch $(OUTS)", +) + +swift_library( + name = "_LocalDebugOptions", + srcs = [":empty"], + copts = [ + "-Xfrontend", + "-serialize-debugging-options", + ], + deps = [ + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/AppBundle:AppBundle", + "//submodules/ObjCRuntimeUtils:ObjCRuntimeUtils", + "//submodules/UIKitRuntimeUtils:UIKitRuntimeUtils", + "//submodules/Crc32:Crc32", + "//submodules/MurMurHash32:MurMurHash32", + "//submodules/StringTransliteration:StringTransliteration", + "//submodules/sqlcipher:sqlcipher", + "//submodules/NumberPluralizationForm:NumberPluralizationForm", + "//submodules/EncryptionProvider:EncryptionProvider", + "//submodules/MtProtoKit:MtProtoKit", + ], + module_name = "_LocalDebugOptions", + tags = ["no-remote"], + visibility = ["//visibility:public"], +) + +debug_deps = select({ + ":debug": [":_LocalDebugOptions"], + "//conditions:default": [], +}) + +filegroup( + name = "AppResources", + srcs = glob([ + "Telegram-iOS/Resources/**/*", + ], exclude = ["Telegram-iOS/Resources/**/.*"]), +) + +filegroup( + name = "AppStringResources", + srcs = glob([ + "Telegram-iOS/*.lproj/Localizable.strings", + ], exclude = ["Telegram-iOS/*.lproj/**/.*"]), +) + +filegroup( + name = "WatchAppStringResources", + srcs = glob([ + "Telegram-iOS/*.lproj/Localizable.strings", + ], exclude = ["Telegram-iOS/*.lproj/**/.*"]), +) + +filegroup( + name = "AppIntentVocabularyResources", + srcs = glob([ + "Telegram-iOS/*.lproj/AppIntentVocabulary.plist", + ], exclude = ["Telegram-iOS/*.lproj/**/.*"]), +) + +filegroup( + name = "InfoPlistStringResources", + srcs = glob([ + "Telegram-iOS/*.lproj/InfoPlist.strings", + ], exclude = ["Telegram-iOS/*.lproj/**/.*"]), +) + +filegroup( + name = "Icons", + srcs = glob([ + "Telegram-iOS/Icons.xcassets/**/*", + ], exclude = ["Telegram-iOS/Icons.xcassets/**/.*"]), +) + +filegroup( + name = "AppIcons", + srcs = glob([ + "Telegram-iOS/AppIcons.xcassets/**/*", + ], exclude = ["Telegram-iOS/AppIcons.xcassets/**/.*"]), +) + +filegroup( + name = "DefaultAppIcon", + srcs = glob([ + "Telegram-iOS/DefaultAppIcon.xcassets/**/*", + ], exclude = ["Telegram-iOS/DefaultAppIcon.xcassets/**/.*"]), +) + +filegroup( + name = "AdditionalIcons", + srcs = glob([ + "Telegram-iOS/*.png", + ]), +) + +filegroup( + name = "LaunchScreen", + srcs = glob([ + "Telegram-iOS/Base.lproj/LaunchScreen.xib", + ]), +) + +objc_library( + name = "Main", + srcs = [ + "Telegram-iOS/main.m" + ], +) + +swift_library( + name = "Lib", + srcs = glob([ + "Telegram-iOS/Application.swift", + ]), + data = [ + ":AppResources", + ":AppIntentVocabularyResources", + ":InfoPlistStringResources", + "//submodules/LegacyComponents:LegacyComponentsResources", + "//submodules/OverlayStatusController:OverlayStatusControllerResources", + "//submodules/PasswordSetupUI:PasswordSetupUIResources", + "//submodules/PasswordSetupUI:PasswordSetupUIAssets", + "//submodules/TelegramUI:TelegramUIResources", + "//submodules/TelegramUI:TelegramUIAssets", + "//submodules/WalletUI:WalletUIResources", + "//submodules/WalletUI:WalletUIAssets", + ], + deps = [ + "//submodules/TelegramUI:TelegramUI", + ], +) + +plist_fragment( + name = "AdditionalInfoPlist", + extension = "plist", + template = + """ + CFBundleShortVersionString + {telegram_version} + CFBundleVersion + {telegram_build_number} + CFBundleURLTypes + + + CFBundleTypeRole + Viewer + CFBundleURLName + {telegram_bundle_id} + CFBundleURLSchemes + + telegram + + + + CFBundleTypeRole + Viewer + CFBundleURLName + {telegram_bundle_id}.ton + CFBundleURLSchemes + + ton + + + + CFBundleTypeRole + Viewer + CFBundleURLName + {telegram_bundle_id}.compatibility + CFBundleURLSchemes + + tg + + + + """.format( + telegram_version = telegram_version, + telegram_build_number = telegram_build_number, + telegram_bundle_id = telegram_bundle_id, + ) +) + +official_apple_pay_merchants = [ + "merchant.ph.telegra.Telegraph", + "merchant.yandex.ph.telegra.Telegraph", + "merchant.sberbank.ph.telegra.Telegraph", + "merchant.sberbank.test.ph.telegra.Telegraph", + "merchant.privatbank.test.telergramios", + "merchant.privatbank.prod.telergram", + "merchant.telegram.tranzzo.test", +] + +official_bundle_ids = [ + "ph.telegra.Telegraph", + "org.telegram.Telegram-iOS", +] + +apple_pay_merchants = official_apple_pay_merchants if telegram_bundle_id == "ph.telegra.Telegraph" else "" + +apple_pay_merchants_fragment = "" if apple_pay_merchants == "" else """ +com.apple.developer.in-app-payments + +""" + "\n".join([ +" {}".format(merchant_id) for merchant_id in apple_pay_merchants +]) + "\n" + """ + +""" + +official_unrestricted_voip_fragment = """ +com.apple.developer.pushkit.unrestricted-voip + +""" +unrestricted_voip_fragment = official_unrestricted_voip_fragment if telegram_bundle_id in official_bundle_ids else "" + +telegram_entitlements_template = """ + com.apple.developer.icloud-services + + CloudKit + CloudDocuments + + com.apple.developer.icloud-container-identifiers + + iCloud.{telegram_bundle_id} + + aps-environment + {telegram_aps_environment} + com.apple.developer.associated-domains + + applinks:telegram.me + applinks:t.me + + com.apple.developer.siri + + com.apple.security.application-groups + + group.{telegram_bundle_id} + + application-identifier + {telegram_team_id}.{telegram_bundle_id} +""" + apple_pay_merchants_fragment + unrestricted_voip_fragment + +plist_fragment( + name = "TelegramEntitlements", + extension = "entitlements", + template = telegram_entitlements_template.format( + telegram_bundle_id = telegram_bundle_id, + telegram_team_id = telegram_team_id, + telegram_aps_environment = telegram_aps_environment, + ) +) + +filegroup( + name = "TelegramWatchExtensionResources", + srcs = glob([ + "Watch/Extension/Resources/**/*", + ], exclude = ["Watch/Extension/Resources/**/.*"]), +) + +filegroup( + name = "TelegramWatchAppResources", + srcs = glob([ + "Watch/Extension/Resources/**/*.png", + ], exclude = ["Watch/Extension/Resources/**/.*"]), +) + +filegroup( + name = "TelegramWatchAppAssets", + srcs = glob([ + "Watch/App/Assets.xcassets/**/*", + ], exclude = ["Watch/App/Assets.xcassets/**/.*"]), +) + +filegroup( + name = "TelegramWatchAppInterface", + srcs = glob([ + "Watch/App/Base.lproj/Interface.storyboard", + ]), +) + +objc_library( + name = "TelegramWatchLib", + srcs = glob([ + "Watch/Extension/**/*.m", + "Watch/SSignalKit/**/*.m", + "Watch/Bridge/**/*.m", + "Watch/WatchCommonWatch/**/*.m", + "Watch/Extension/**/*.h", + "Watch/SSignalKit/**/*.h", + "Watch/Bridge/**/*.h", + "Watch/WatchCommonWatch/**/*.h", + ]), + copts = [ + "-DTARGET_OS_WATCH=1", + "-ITelegram/Watch", + "-ITelegram/Watch/Extension", + "-ITelegram/Watch/Bridge", + ], + sdk_frameworks = [ + "WatchKit", + "WatchConnectivity", + "ClockKit", + "UserNotifications", + "CoreLocation", + "CoreGraphics", + ], +) + +plist_fragment( + name = "VersionInfoPlist", + extension = "plist", + template = + """ + CFBundleShortVersionString + {telegram_version} + CFBundleVersion + {telegram_build_number} + """.format( + telegram_version = telegram_version, + telegram_build_number = telegram_build_number, + ) +) + +plist_fragment( + name = "AppNameInfoPlist", + extension = "plist", + template = + """ + CFBundleDisplayName + Telegram + """ +) + +plist_fragment( + name = "WatchExtensionNSExtensionInfoPlist", + extension = "plist", + template = + """ + NSExtension + + NSExtensionAttributes + + WKAppBundleIdentifier + {telegram_bundle_id}.watchkitapp + + NSExtensionPointIdentifier + com.apple.watchkit + + """.format( + telegram_bundle_id = telegram_bundle_id, + ) +) + +plist_fragment( + name = "WatchAppCompanionInfoPlist", + extension = "plist", + template = + """ + WKCompanionAppBundleIdentifier + {telegram_bundle_id} + """.format( + telegram_bundle_id = telegram_bundle_id, + ) +) + +plist_fragment( + name = "WatchExtensionInfoPlist", + extension = "plist", + template = + """ + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + {telegram_bundle_id}.watchkitapp.watchkitextension + CFBundleName + Telegram + CFBundlePackageType + XPC! + WKExtensionDelegateClassName + TGExtensionDelegate + """.format( + telegram_bundle_id = telegram_bundle_id, + ) +) + +plist_fragment( + name = "WatchAppInfoPlist", + extension = "plist", + template = + """ + CFBundleDevelopmentRegion + en + CFBundleIdentifier + {telegram_bundle_id}.watchkitapp + CFBundleName + Telegram + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + WKWatchKitApp + + """.format( + telegram_bundle_id = telegram_bundle_id, + ) +) + +watchos_extension( + name = "TelegramWatchExtension", + bundle_id = "{telegram_bundle_id}.watchkitapp.watchkitextension".format( + telegram_bundle_id = telegram_bundle_id, + ), + bundle_name = "TelegramWatchExtension", + infoplists = [ + ":WatchExtensionInfoPlist", + ":VersionInfoPlist", + ":AppNameInfoPlist", + ":WatchExtensionNSExtensionInfoPlist", + ], + minimum_os_version = "5.0", + provisioning_profile = "//build-input/data/provisioning-profiles:WatchExtension.mobileprovision", + resources = [ + ":TelegramWatchExtensionResources", + ], + strings = [ + ":WatchAppStringResources", + ], + deps = [ + ":TelegramWatchLib", + ], +) + +watchos_application( + name = "TelegramWatchApp", + #app_icons = , + bundle_id = "{telegram_bundle_id}.watchkitapp".format( + telegram_bundle_id = telegram_bundle_id, + ), + bundle_name = "TelegramWatch", + extension = ":TelegramWatchExtension", + infoplists = [ + ":WatchAppInfoPlist", + ":VersionInfoPlist", + ":AppNameInfoPlist", + ":WatchAppCompanionInfoPlist", + ], + minimum_os_version = "5.0", + provisioning_profile = "//build-input/data/provisioning-profiles:WatchApp.mobileprovision", + resources = [ + ":TelegramWatchAppResources", + ":TelegramWatchAppAssets", + ], + storyboards = [ + ":TelegramWatchAppInterface", + ], + strings = [ + ], +) + +swift_library( + name = "ShareExtensionLib", + module_name = "ShareExtensionLib", + srcs = glob([ + "Share/**/*.swift", + ]), + deps = [ + "//submodules/TelegramUI:TelegramUI" + ], +) + +plist_fragment( + name = "TelegramUIInfoPlist", + extension = "plist", + template = + """ + CFBundleIdentifier + {telegram_bundle_id}.TelegramUI + CFBundleVersion + {telegram_build_number} + CFBundleDevelopmentRegion + en + CFBundleName + TelegramUI + CFBundleShortVersionString + {telegram_version} + """.format( + telegram_bundle_id = telegram_bundle_id, + telegram_version = telegram_version, + telegram_build_number = telegram_build_number, + ) +) + +ios_framework( + name = "TelegramUIFramework", + bundle_id = "{telegram_bundle_id}.TelegramUI".format( + telegram_bundle_id = telegram_bundle_id, + ), + families = [ + "iphone", + "ipad", + ], + infoplists = [ + ":TelegramUIInfoPlist", + ], + minimum_os_version = "9.0", + deps = [ + "//submodules/TelegramUI:TelegramUI", + ] + debug_deps, +) + +plist_fragment( + name = "ShareInfoPlist", + extension = "plist", + template = + """ + CFBundleDevelopmentRegion + en + CFBundleIdentifier + {telegram_bundle_id}.Share + CFBundleName + Telegram + CFBundlePackageType + XPC! + NSExtension + + NSExtensionAttributes + + IntentsSupported + + INSendMessageIntent + + NSExtensionActivationRule + SUBQUERY ( + extensionItems, + $extensionItem, + SUBQUERY ( + $extensionItem.attachments, + $attachment, + ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.file-url" || + ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.movie" || + ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.image" || + ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.url" || + ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.text" || + ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.audio" || + ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.data" || + ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.vcard" || + ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "com.apple.pkpass" + ).@count == $extensionItem.attachments.@count +).@count > 0 + + NSExtensionPointIdentifier + com.apple.share-services + NSExtensionPrincipalClass + ShareRootController + + """.format( + telegram_bundle_id = telegram_bundle_id, + ) +) + +ios_extension( + name = "ShareExtension", + bundle_id = "{telegram_bundle_id}.Share".format( + telegram_bundle_id = telegram_bundle_id, + ), + families = [ + "iphone", + "ipad", + ], + infoplists = [ + ":ShareInfoPlist", + ":VersionInfoPlist", + ":AppNameInfoPlist", + ], + minimum_os_version = "9.0", + provisioning_profile = "//build-input/data/provisioning-profiles:Share.mobileprovision", + deps = [":ShareExtensionLib"], + frameworks = [ + ":TelegramUIFramework" + ], +) + +plist_fragment( + name = "TelegramInfoPlist", + extension = "plist", + template = + """ + BGTaskSchedulerPermittedIdentifiers + + {telegram_bundle_id}.refresh + + CFBundleAllowMixedLocalizations + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + Telegram + CFBundleIdentifier + {telegram_bundle_id} + CFBundleName + Telegram + ITSAppUsesNonExemptEncryption + + LSApplicationQueriesSchemes + + instagram + comgooglemaps-x-callback + foursquare + here-location + yandexmaps + yandexnavi + comgooglemaps + youtube + twitter + vk + waze + googlechrome + firefox + touch-http + yandexbrowser-open-url + vimeo + vine + coub + uber + citymapper + lyft + opera-http + firefox-focus + ddgQuickLink + moovit + alook + dgis + microsoft-edge-http + brave + onionhttp + ucbrowser + dolphin + + LSRequiresIPhoneOS + + NSAppTransportSecurity + + 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 + + UIDeviceFamily + + 1 + 2 + + UIFileSharingEnabled + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UIRequiresPersistentWiFi + + UIStatusBarStyle + UIStatusBarStyleDefault + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + UIViewEdgeAntialiasing + + 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 + + + + + """.format( + telegram_bundle_id = telegram_bundle_id, + ) +) + +ios_application( + name = "Telegram", + bundle_id = "{telegram_bundle_id}".format( + telegram_bundle_id = telegram_bundle_id, + ), + families = ["iphone", "ipad"], + minimum_os_version = "9.0", + provisioning_profile = "//build-input/data/provisioning-profiles:Telegram.mobileprovision", + entitlements = ":TelegramEntitlements.entitlements", + infoplists = [ + ":TelegramInfoPlist", + ":AdditionalInfoPlist", + ], + app_icons = [ + ":DefaultAppIcon", + ], + frameworks = [ + ":TelegramUIFramework", + ], + strings = [ + ":AppStringResources", + ], + extensions = [ + ":ShareExtension", + ], + watch_application = ":TelegramWatchApp", + deps = [ + ":Main", + ":Lib", + ], +) diff --git a/NotificationContent/Info.plist b/Telegram/NotificationContent/Info.plist similarity index 100% rename from NotificationContent/Info.plist rename to Telegram/NotificationContent/Info.plist diff --git a/NotificationContent/NotificationContent-Bridging-Header.h b/Telegram/NotificationContent/NotificationContent-Bridging-Header.h similarity index 100% rename from NotificationContent/NotificationContent-Bridging-Header.h rename to Telegram/NotificationContent/NotificationContent-Bridging-Header.h diff --git a/NotificationContent/NotificationViewController.swift b/Telegram/NotificationContent/NotificationViewController.swift similarity index 100% rename from NotificationContent/NotificationViewController.swift rename to Telegram/NotificationContent/NotificationViewController.swift diff --git a/NotificationService/Api.h b/Telegram/NotificationService/Api.h similarity index 100% rename from NotificationService/Api.h rename to Telegram/NotificationService/Api.h diff --git a/NotificationService/Api.m b/Telegram/NotificationService/Api.m similarity index 100% rename from NotificationService/Api.m rename to Telegram/NotificationService/Api.m diff --git a/NotificationService/ApplicationSpecificSharedDataKeys.swift b/Telegram/NotificationService/ApplicationSpecificSharedDataKeys.swift similarity index 100% rename from NotificationService/ApplicationSpecificSharedDataKeys.swift rename to Telegram/NotificationService/ApplicationSpecificSharedDataKeys.swift diff --git a/NotificationService/Attachments.h b/Telegram/NotificationService/Attachments.h similarity index 100% rename from NotificationService/Attachments.h rename to Telegram/NotificationService/Attachments.h diff --git a/NotificationService/Attachments.m b/Telegram/NotificationService/Attachments.m similarity index 90% rename from NotificationService/Attachments.m rename to Telegram/NotificationService/Attachments.m index 29d498dc2d..28ae322de9 100644 --- a/NotificationService/Attachments.m +++ b/Telegram/NotificationService/Attachments.m @@ -1,10 +1,6 @@ #import "Attachments.h" -#ifdef BUCK #import -#else -#import -#endif #import "Api.h" diff --git a/NotificationService/FetchImage.h b/Telegram/NotificationService/FetchImage.h similarity index 100% rename from NotificationService/FetchImage.h rename to Telegram/NotificationService/FetchImage.h diff --git a/NotificationService/FetchImage.m b/Telegram/NotificationService/FetchImage.m similarity index 100% rename from NotificationService/FetchImage.m rename to Telegram/NotificationService/FetchImage.m diff --git a/NotificationService/InAppNotificationSettings.swift b/Telegram/NotificationService/InAppNotificationSettings.swift similarity index 100% rename from NotificationService/InAppNotificationSettings.swift rename to Telegram/NotificationService/InAppNotificationSettings.swift diff --git a/NotificationService/Info.plist b/Telegram/NotificationService/Info.plist similarity index 100% rename from NotificationService/Info.plist rename to Telegram/NotificationService/Info.plist diff --git a/NotificationService/Namespaces.swift b/Telegram/NotificationService/Namespaces.swift similarity index 100% rename from NotificationService/Namespaces.swift rename to Telegram/NotificationService/Namespaces.swift diff --git a/NotificationService/NotificationService-Bridging-Header.h b/Telegram/NotificationService/NotificationService-Bridging-Header.h similarity index 100% rename from NotificationService/NotificationService-Bridging-Header.h rename to Telegram/NotificationService/NotificationService-Bridging-Header.h diff --git a/NotificationService/NotificationService.h b/Telegram/NotificationService/NotificationService.h similarity index 100% rename from NotificationService/NotificationService.h rename to Telegram/NotificationService/NotificationService.h diff --git a/NotificationService/NotificationService.m b/Telegram/NotificationService/NotificationService.m similarity index 100% rename from NotificationService/NotificationService.m rename to Telegram/NotificationService/NotificationService.m diff --git a/NotificationService/NotificationService.swift b/Telegram/NotificationService/NotificationService.swift similarity index 100% rename from NotificationService/NotificationService.swift rename to Telegram/NotificationService/NotificationService.swift diff --git a/NotificationService/Serialization.h b/Telegram/NotificationService/Serialization.h similarity index 70% rename from NotificationService/Serialization.h rename to Telegram/NotificationService/Serialization.h index d2beb976d6..1089c5a010 100644 --- a/NotificationService/Serialization.h +++ b/Telegram/NotificationService/Serialization.h @@ -1,10 +1,6 @@ #import -#ifdef BUCK #import -#else -#import -#endif NS_ASSUME_NONNULL_BEGIN diff --git a/NotificationService/Serialization.m b/Telegram/NotificationService/Serialization.m similarity index 100% rename from NotificationService/Serialization.m rename to Telegram/NotificationService/Serialization.m diff --git a/NotificationService/StoredAccountInfos.h b/Telegram/NotificationService/StoredAccountInfos.h similarity index 100% rename from NotificationService/StoredAccountInfos.h rename to Telegram/NotificationService/StoredAccountInfos.h diff --git a/NotificationService/StoredAccountInfos.m b/Telegram/NotificationService/StoredAccountInfos.m similarity index 99% rename from NotificationService/StoredAccountInfos.m rename to Telegram/NotificationService/StoredAccountInfos.m index 4f60eac49b..424aa3aefc 100644 --- a/NotificationService/StoredAccountInfos.m +++ b/Telegram/NotificationService/StoredAccountInfos.m @@ -1,10 +1,6 @@ #import "StoredAccountInfos.h" -#ifdef BUCK #import -#else -#import -#endif #import diff --git a/NotificationService/Sync.swift b/Telegram/NotificationService/Sync.swift similarity index 100% rename from NotificationService/Sync.swift rename to Telegram/NotificationService/Sync.swift diff --git a/NotificationService/TelegramChannel.swift b/Telegram/NotificationService/TelegramChannel.swift similarity index 100% rename from NotificationService/TelegramChannel.swift rename to Telegram/NotificationService/TelegramChannel.swift diff --git a/Share/Info.plist b/Telegram/Share/Info.plist similarity index 100% rename from Share/Info.plist rename to Telegram/Share/Info.plist diff --git a/Share/Share-Bridging-Header.h b/Telegram/Share/Share-Bridging-Header.h similarity index 100% rename from Share/Share-Bridging-Header.h rename to Telegram/Share/Share-Bridging-Header.h diff --git a/Share/ShareRootController.swift b/Telegram/Share/ShareRootController.swift similarity index 100% rename from Share/ShareRootController.swift rename to Telegram/Share/ShareRootController.swift diff --git a/Share/en.lproj/Localizable.strings b/Telegram/Share/en.lproj/Localizable.strings similarity index 100% rename from Share/en.lproj/Localizable.strings rename to Telegram/Share/en.lproj/Localizable.strings diff --git a/SiriIntents/Info.plist b/Telegram/SiriIntents/Info.plist similarity index 100% rename from SiriIntents/Info.plist rename to Telegram/SiriIntents/Info.plist diff --git a/SiriIntents/IntentContacts.swift b/Telegram/SiriIntents/IntentContacts.swift similarity index 100% rename from SiriIntents/IntentContacts.swift rename to Telegram/SiriIntents/IntentContacts.swift diff --git a/SiriIntents/IntentHandler.swift b/Telegram/SiriIntents/IntentHandler.swift similarity index 100% rename from SiriIntents/IntentHandler.swift rename to Telegram/SiriIntents/IntentHandler.swift diff --git a/SiriIntents/IntentMessages.swift b/Telegram/SiriIntents/IntentMessages.swift similarity index 99% rename from SiriIntents/IntentMessages.swift rename to Telegram/SiriIntents/IntentMessages.swift index f8add17d11..4de0ef0384 100644 --- a/SiriIntents/IntentMessages.swift +++ b/Telegram/SiriIntents/IntentMessages.swift @@ -37,7 +37,7 @@ func unreadMessages(account: Account) -> Signal<[INMessage], NoError> { |> mapToSignal { view -> Signal<[INMessage], NoError> in var signals: [Signal<[INMessage], NoError>] = [] for entry in view.0.entries { - if case let .MessageEntry(index, _, readState, notificationSettings, _, _, _, _, _) = entry { + if case let .MessageEntry(index, _, readState, notificationSettings, _, _, _, _, _, _) = entry { if index.messageIndex.id.peerId.namespace != Namespaces.Peer.CloudUser { continue } diff --git a/SiriIntents/SiriIntents-Bridging-Header.h b/Telegram/SiriIntents/SiriIntents-Bridging-Header.h similarity index 100% rename from SiriIntents/SiriIntents-Bridging-Header.h rename to Telegram/SiriIntents/SiriIntents-Bridging-Header.h diff --git a/SupportFiles/Empty.swift b/Telegram/SupportFiles/Empty.swift similarity index 100% rename from SupportFiles/Empty.swift rename to Telegram/SupportFiles/Empty.swift diff --git a/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Contents.json b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Contents.json similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Contents.json rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Contents.json diff --git a/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@120x120-1.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@120x120-1.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@120x120-1.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@120x120-1.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@120x120.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@120x120.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@120x120.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@120x120.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@152x152.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@152x152.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@152x152.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@152x152.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@167x167.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@167x167.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@167x167.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@167x167.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@180x180.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@180x180.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@180x180.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@180x180.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@20x20.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@20x20.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@20x20.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@20x20.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@29x29.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@29x29.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@29x29.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@29x29.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@40x40-1.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@40x40-1.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@40x40-1.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@40x40-1.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@40x40-2.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@40x40-2.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@40x40-2.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@40x40-2.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@40x40.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@40x40.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@40x40.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@40x40.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@58x58-2.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@58x58-2.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@58x58-2.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@58x58-2.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@58x58.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@58x58.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@58x58.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@58x58.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@60x60.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@60x60.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@60x60.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@60x60.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@76x76.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@76x76.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@76x76.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@76x76.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@80x80-1.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@80x80-1.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@80x80-1.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@80x80-1.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@80x80.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@80x80.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@80x80.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@80x80.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@87x87.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@87x87.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@87x87.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackFilledIcon.appiconset/Icon4@87x87.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Contents.json b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Contents.json similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Contents.json rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Contents.json diff --git a/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@120x120-1.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@120x120-1.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@120x120-1.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@120x120-1.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@120x120.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@120x120.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@120x120.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@120x120.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@152x152.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@152x152.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@152x152.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@152x152.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@167x167.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@167x167.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@167x167.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@167x167.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@180x180.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@180x180.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@180x180.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@180x180.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@20x20.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@20x20.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@20x20.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@20x20.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@29x29.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@29x29.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@29x29.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@29x29.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@40x40-1.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@40x40-1.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@40x40-1.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@40x40-1.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@40x40-2.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@40x40-2.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@40x40-2.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@40x40-2.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@40x40.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@40x40.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@40x40.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@40x40.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@58x58-1.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@58x58-1.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@58x58-1.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@58x58-1.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@58x58.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@58x58.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@58x58.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@58x58.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@60x60.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@60x60.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@60x60.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@60x60.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@76x76.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@76x76.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@76x76.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@76x76.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@80x80-1.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@80x80-1.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@80x80-1.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@80x80-1.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@80x80.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@80x80.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@80x80.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@80x80.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@87x87.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@87x87.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@87x87.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlackIcon.appiconset/Icon2@87x87.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Contents.json b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Contents.json similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Contents.json rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Contents.json diff --git a/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@120x120-1.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@120x120-1.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@120x120-1.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@120x120-1.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@120x120.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@120x120.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@120x120.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@120x120.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@152x152.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@152x152.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@152x152.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@152x152.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@167x167.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@167x167.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@167x167.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@167x167.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@180x180.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@180x180.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@180x180.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@180x180.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@20x20.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@20x20.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@20x20.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@20x20.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@29x29.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@29x29.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@29x29.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@29x29.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@40x40-1.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@40x40-1.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@40x40-1.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@40x40-1.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@40x40-2.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@40x40-2.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@40x40-2.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@40x40-2.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@40x40.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@40x40.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@40x40.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@40x40.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@58x58-1.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@58x58-1.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@58x58-1.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@58x58-1.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@58x58.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@58x58.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@58x58.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@58x58.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@60x60.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@60x60.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@60x60.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@60x60.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@76x76.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@76x76.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@76x76.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@76x76.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@80x80-1.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@80x80-1.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@80x80-1.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@80x80-1.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@80x80.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@80x80.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@80x80.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@80x80.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@87x87.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@87x87.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@87x87.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueFilledIcon.appiconset/Icon3@87x87.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Contents.json b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Contents.json similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Contents.json rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Contents.json diff --git a/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@120x120-1.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@120x120-1.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@120x120-1.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@120x120-1.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@120x120.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@120x120.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@120x120.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@120x120.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@152x152.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@152x152.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@152x152.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@152x152.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@167x167.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@167x167.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@167x167.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@167x167.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@180x180.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@180x180.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@180x180.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@180x180.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@20x20.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@20x20.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@20x20.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@20x20.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@29x29.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@29x29.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@29x29.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@29x29.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@40x40-1.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@40x40-1.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@40x40-1.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@40x40-1.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@40x40-2.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@40x40-2.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@40x40-2.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@40x40-2.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@40x40.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@40x40.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@40x40.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@40x40.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@58x58-1.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@58x58-1.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@58x58-1.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@58x58-1.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@58x58.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@58x58.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@58x58.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@58x58.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@60x60.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@60x60.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@60x60.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@60x60.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@76x76.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@76x76.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@76x76.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@76x76.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@80x80-1.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@80x80-1.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@80x80-1.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@80x80-1.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@80x80.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@80x80.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@80x80.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@80x80.png diff --git a/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@87x87.png b/Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@87x87.png similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@87x87.png rename to Telegram/Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/Icon1@87x87.png diff --git a/Telegram-iOS/AppIcons.xcassets/Contents.json b/Telegram/Telegram-iOS/AppIcons.xcassets/Contents.json similarity index 100% rename from Telegram-iOS/AppIcons.xcassets/Contents.json rename to Telegram/Telegram-iOS/AppIcons.xcassets/Contents.json diff --git a/Telegram-iOS/Application.swift b/Telegram/Telegram-iOS/Application.swift similarity index 100% rename from Telegram-iOS/Application.swift rename to Telegram/Telegram-iOS/Application.swift diff --git a/Telegram-iOS/Base.lproj/LaunchScreen.xib b/Telegram/Telegram-iOS/Base.lproj/LaunchScreen.xib similarity index 100% rename from Telegram-iOS/Base.lproj/LaunchScreen.xib rename to Telegram/Telegram-iOS/Base.lproj/LaunchScreen.xib diff --git a/Telegram-iOS/BlackClassicIcon@2x.png b/Telegram/Telegram-iOS/BlackClassicIcon@2x.png similarity index 100% rename from Telegram-iOS/BlackClassicIcon@2x.png rename to Telegram/Telegram-iOS/BlackClassicIcon@2x.png diff --git a/Telegram-iOS/BlackClassicIcon@3x.png b/Telegram/Telegram-iOS/BlackClassicIcon@3x.png similarity index 100% rename from Telegram-iOS/BlackClassicIcon@3x.png rename to Telegram/Telegram-iOS/BlackClassicIcon@3x.png diff --git a/Telegram-iOS/BlackClassicIconIpad.png b/Telegram/Telegram-iOS/BlackClassicIconIpad.png similarity index 100% rename from Telegram-iOS/BlackClassicIconIpad.png rename to Telegram/Telegram-iOS/BlackClassicIconIpad.png diff --git a/Telegram-iOS/BlackClassicIconIpad@2x.png b/Telegram/Telegram-iOS/BlackClassicIconIpad@2x.png similarity index 100% rename from Telegram-iOS/BlackClassicIconIpad@2x.png rename to Telegram/Telegram-iOS/BlackClassicIconIpad@2x.png diff --git a/Telegram-iOS/BlackClassicIconLargeIpad@2x.png b/Telegram/Telegram-iOS/BlackClassicIconLargeIpad@2x.png similarity index 100% rename from Telegram-iOS/BlackClassicIconLargeIpad@2x.png rename to Telegram/Telegram-iOS/BlackClassicIconLargeIpad@2x.png diff --git a/Telegram-iOS/BlackClassicNotificationIcon.png b/Telegram/Telegram-iOS/BlackClassicNotificationIcon.png similarity index 100% rename from Telegram-iOS/BlackClassicNotificationIcon.png rename to Telegram/Telegram-iOS/BlackClassicNotificationIcon.png diff --git a/Telegram-iOS/BlackClassicNotificationIcon@2x.png b/Telegram/Telegram-iOS/BlackClassicNotificationIcon@2x.png similarity index 100% rename from Telegram-iOS/BlackClassicNotificationIcon@2x.png rename to Telegram/Telegram-iOS/BlackClassicNotificationIcon@2x.png diff --git a/Telegram-iOS/BlackClassicNotificationIcon@3x.png b/Telegram/Telegram-iOS/BlackClassicNotificationIcon@3x.png similarity index 100% rename from Telegram-iOS/BlackClassicNotificationIcon@3x.png rename to Telegram/Telegram-iOS/BlackClassicNotificationIcon@3x.png diff --git a/Telegram-iOS/BlackFilledIcon@2x.png b/Telegram/Telegram-iOS/BlackFilledIcon@2x.png similarity index 100% rename from Telegram-iOS/BlackFilledIcon@2x.png rename to Telegram/Telegram-iOS/BlackFilledIcon@2x.png diff --git a/Telegram-iOS/BlackFilledIcon@3x.png b/Telegram/Telegram-iOS/BlackFilledIcon@3x.png similarity index 100% rename from Telegram-iOS/BlackFilledIcon@3x.png rename to Telegram/Telegram-iOS/BlackFilledIcon@3x.png diff --git a/Telegram-iOS/BlackFilledIconIpad.png b/Telegram/Telegram-iOS/BlackFilledIconIpad.png similarity index 100% rename from Telegram-iOS/BlackFilledIconIpad.png rename to Telegram/Telegram-iOS/BlackFilledIconIpad.png diff --git a/Telegram-iOS/BlackFilledIconIpad@2x.png b/Telegram/Telegram-iOS/BlackFilledIconIpad@2x.png similarity index 100% rename from Telegram-iOS/BlackFilledIconIpad@2x.png rename to Telegram/Telegram-iOS/BlackFilledIconIpad@2x.png diff --git a/Telegram-iOS/BlackFilledIconLargeIpad@2x.png b/Telegram/Telegram-iOS/BlackFilledIconLargeIpad@2x.png similarity index 100% rename from Telegram-iOS/BlackFilledIconLargeIpad@2x.png rename to Telegram/Telegram-iOS/BlackFilledIconLargeIpad@2x.png diff --git a/Telegram-iOS/BlackIcon@2x.png b/Telegram/Telegram-iOS/BlackIcon@2x.png similarity index 100% rename from Telegram-iOS/BlackIcon@2x.png rename to Telegram/Telegram-iOS/BlackIcon@2x.png diff --git a/Telegram-iOS/BlackIcon@3x.png b/Telegram/Telegram-iOS/BlackIcon@3x.png similarity index 100% rename from Telegram-iOS/BlackIcon@3x.png rename to Telegram/Telegram-iOS/BlackIcon@3x.png diff --git a/Telegram-iOS/BlackIconIpad.png b/Telegram/Telegram-iOS/BlackIconIpad.png similarity index 100% rename from Telegram-iOS/BlackIconIpad.png rename to Telegram/Telegram-iOS/BlackIconIpad.png diff --git a/Telegram-iOS/BlackIconIpad@2x.png b/Telegram/Telegram-iOS/BlackIconIpad@2x.png similarity index 100% rename from Telegram-iOS/BlackIconIpad@2x.png rename to Telegram/Telegram-iOS/BlackIconIpad@2x.png diff --git a/Telegram-iOS/BlackIconLargeIpad@2x.png b/Telegram/Telegram-iOS/BlackIconLargeIpad@2x.png similarity index 100% rename from Telegram-iOS/BlackIconLargeIpad@2x.png rename to Telegram/Telegram-iOS/BlackIconLargeIpad@2x.png diff --git a/Telegram-iOS/BlackNotificationIcon.png b/Telegram/Telegram-iOS/BlackNotificationIcon.png similarity index 100% rename from Telegram-iOS/BlackNotificationIcon.png rename to Telegram/Telegram-iOS/BlackNotificationIcon.png diff --git a/Telegram-iOS/BlackNotificationIcon@2x.png b/Telegram/Telegram-iOS/BlackNotificationIcon@2x.png similarity index 100% rename from Telegram-iOS/BlackNotificationIcon@2x.png rename to Telegram/Telegram-iOS/BlackNotificationIcon@2x.png diff --git a/Telegram-iOS/BlackNotificationIcon@3x.png b/Telegram/Telegram-iOS/BlackNotificationIcon@3x.png similarity index 100% rename from Telegram-iOS/BlackNotificationIcon@3x.png rename to Telegram/Telegram-iOS/BlackNotificationIcon@3x.png diff --git a/Telegram-iOS/BlueClassicIcon@2x.png b/Telegram/Telegram-iOS/BlueClassicIcon@2x.png similarity index 100% rename from Telegram-iOS/BlueClassicIcon@2x.png rename to Telegram/Telegram-iOS/BlueClassicIcon@2x.png diff --git a/Telegram-iOS/BlueClassicIcon@3x.png b/Telegram/Telegram-iOS/BlueClassicIcon@3x.png similarity index 100% rename from Telegram-iOS/BlueClassicIcon@3x.png rename to Telegram/Telegram-iOS/BlueClassicIcon@3x.png diff --git a/Telegram-iOS/BlueClassicIconIpad.png b/Telegram/Telegram-iOS/BlueClassicIconIpad.png similarity index 100% rename from Telegram-iOS/BlueClassicIconIpad.png rename to Telegram/Telegram-iOS/BlueClassicIconIpad.png diff --git a/Telegram-iOS/BlueClassicIconIpad@2x.png b/Telegram/Telegram-iOS/BlueClassicIconIpad@2x.png similarity index 100% rename from Telegram-iOS/BlueClassicIconIpad@2x.png rename to Telegram/Telegram-iOS/BlueClassicIconIpad@2x.png diff --git a/Telegram-iOS/BlueClassicIconLargeIpad@2x.png b/Telegram/Telegram-iOS/BlueClassicIconLargeIpad@2x.png similarity index 100% rename from Telegram-iOS/BlueClassicIconLargeIpad@2x.png rename to Telegram/Telegram-iOS/BlueClassicIconLargeIpad@2x.png diff --git a/Telegram-iOS/BlueClassicNotificationIcon.png b/Telegram/Telegram-iOS/BlueClassicNotificationIcon.png similarity index 100% rename from Telegram-iOS/BlueClassicNotificationIcon.png rename to Telegram/Telegram-iOS/BlueClassicNotificationIcon.png diff --git a/Telegram-iOS/BlueClassicNotificationIcon@2x.png b/Telegram/Telegram-iOS/BlueClassicNotificationIcon@2x.png similarity index 100% rename from Telegram-iOS/BlueClassicNotificationIcon@2x.png rename to Telegram/Telegram-iOS/BlueClassicNotificationIcon@2x.png diff --git a/Telegram-iOS/BlueClassicNotificationIcon@3x.png b/Telegram/Telegram-iOS/BlueClassicNotificationIcon@3x.png similarity index 100% rename from Telegram-iOS/BlueClassicNotificationIcon@3x.png rename to Telegram/Telegram-iOS/BlueClassicNotificationIcon@3x.png diff --git a/Telegram-iOS/BlueFilledIcon@2x.png b/Telegram/Telegram-iOS/BlueFilledIcon@2x.png similarity index 100% rename from Telegram-iOS/BlueFilledIcon@2x.png rename to Telegram/Telegram-iOS/BlueFilledIcon@2x.png diff --git a/Telegram-iOS/BlueFilledIcon@3x.png b/Telegram/Telegram-iOS/BlueFilledIcon@3x.png similarity index 100% rename from Telegram-iOS/BlueFilledIcon@3x.png rename to Telegram/Telegram-iOS/BlueFilledIcon@3x.png diff --git a/Telegram-iOS/BlueFilledIconIpad.png b/Telegram/Telegram-iOS/BlueFilledIconIpad.png similarity index 100% rename from Telegram-iOS/BlueFilledIconIpad.png rename to Telegram/Telegram-iOS/BlueFilledIconIpad.png diff --git a/Telegram-iOS/BlueFilledIconIpad@2x.png b/Telegram/Telegram-iOS/BlueFilledIconIpad@2x.png similarity index 100% rename from Telegram-iOS/BlueFilledIconIpad@2x.png rename to Telegram/Telegram-iOS/BlueFilledIconIpad@2x.png diff --git a/Telegram-iOS/BlueFilledIconLargeIpad@2x.png b/Telegram/Telegram-iOS/BlueFilledIconLargeIpad@2x.png similarity index 100% rename from Telegram-iOS/BlueFilledIconLargeIpad@2x.png rename to Telegram/Telegram-iOS/BlueFilledIconLargeIpad@2x.png diff --git a/Telegram-iOS/BlueIcon@2x.png b/Telegram/Telegram-iOS/BlueIcon@2x.png similarity index 100% rename from Telegram-iOS/BlueIcon@2x.png rename to Telegram/Telegram-iOS/BlueIcon@2x.png diff --git a/Telegram-iOS/BlueIcon@3x.png b/Telegram/Telegram-iOS/BlueIcon@3x.png similarity index 100% rename from Telegram-iOS/BlueIcon@3x.png rename to Telegram/Telegram-iOS/BlueIcon@3x.png diff --git a/Telegram-iOS/BlueIconIpad.png b/Telegram/Telegram-iOS/BlueIconIpad.png similarity index 100% rename from Telegram-iOS/BlueIconIpad.png rename to Telegram/Telegram-iOS/BlueIconIpad.png diff --git a/Telegram-iOS/BlueIconIpad@2x.png b/Telegram/Telegram-iOS/BlueIconIpad@2x.png similarity index 100% rename from Telegram-iOS/BlueIconIpad@2x.png rename to Telegram/Telegram-iOS/BlueIconIpad@2x.png diff --git a/Telegram-iOS/BlueIconLargeIpad@2x.png b/Telegram/Telegram-iOS/BlueIconLargeIpad@2x.png similarity index 100% rename from Telegram-iOS/BlueIconLargeIpad@2x.png rename to Telegram/Telegram-iOS/BlueIconLargeIpad@2x.png diff --git a/Telegram-iOS/BlueNotificationIcon.png b/Telegram/Telegram-iOS/BlueNotificationIcon.png similarity index 100% rename from Telegram-iOS/BlueNotificationIcon.png rename to Telegram/Telegram-iOS/BlueNotificationIcon.png diff --git a/Telegram-iOS/BlueNotificationIcon@2x.png b/Telegram/Telegram-iOS/BlueNotificationIcon@2x.png similarity index 100% rename from Telegram-iOS/BlueNotificationIcon@2x.png rename to Telegram/Telegram-iOS/BlueNotificationIcon@2x.png diff --git a/Telegram-iOS/BlueNotificationIcon@3x.png b/Telegram/Telegram-iOS/BlueNotificationIcon@3x.png similarity index 100% rename from Telegram-iOS/BlueNotificationIcon@3x.png rename to Telegram/Telegram-iOS/BlueNotificationIcon@3x.png diff --git a/Telegram-iOS/Config-AppStoreLLC.xcconfig b/Telegram/Telegram-iOS/Config-AppStoreLLC.xcconfig similarity index 100% rename from Telegram-iOS/Config-AppStoreLLC.xcconfig rename to Telegram/Telegram-iOS/Config-AppStoreLLC.xcconfig diff --git a/Telegram-iOS/Config-Fork.xcconfig b/Telegram/Telegram-iOS/Config-Fork.xcconfig similarity index 100% rename from Telegram-iOS/Config-Fork.xcconfig rename to Telegram/Telegram-iOS/Config-Fork.xcconfig diff --git a/Telegram-iOS/Config-Hockeyapp-Internal.xcconfig b/Telegram/Telegram-iOS/Config-Hockeyapp-Internal.xcconfig similarity index 100% rename from Telegram-iOS/Config-Hockeyapp-Internal.xcconfig rename to Telegram/Telegram-iOS/Config-Hockeyapp-Internal.xcconfig diff --git a/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIcon@2x-1.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueIcon@2x-1.png similarity index 100% rename from Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIcon@2x-1.png rename to Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueIcon@2x-1.png diff --git a/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIcon@2x.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueIcon@2x.png similarity index 100% rename from Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIcon@2x.png rename to Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueIcon@2x.png diff --git a/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIcon@3x.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueIcon@3x.png similarity index 100% rename from Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIcon@3x.png rename to Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueIcon@3x.png diff --git a/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIconIpad.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueIconIpad.png similarity index 100% rename from Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIconIpad.png rename to Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueIconIpad.png diff --git a/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIconIpad@2x.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueIconIpad@2x.png similarity index 100% rename from Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIconIpad@2x.png rename to Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueIconIpad@2x.png diff --git a/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIconLargeIpad@2x.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueIconLargeIpad@2x.png similarity index 100% rename from Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIconLargeIpad@2x.png rename to Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueIconLargeIpad@2x.png diff --git a/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueNotificationIcon.png similarity index 100% rename from Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon.png rename to Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueNotificationIcon.png diff --git a/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@2x-1.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@2x-1.png similarity index 100% rename from Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@2x-1.png rename to Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@2x-1.png diff --git a/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@2x.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@2x.png similarity index 100% rename from Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@2x.png rename to Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@2x.png diff --git a/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@3x.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@3x.png similarity index 100% rename from Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@3x.png rename to Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@3x.png diff --git a/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Contents.json b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Contents.json similarity index 100% rename from Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Contents.json rename to Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Contents.json diff --git a/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple-iTunesArtwork.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple-iTunesArtwork.png similarity index 100% rename from Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple-iTunesArtwork.png rename to Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple-iTunesArtwork.png diff --git a/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@29x29.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@29x29.png similarity index 100% rename from Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@29x29.png rename to Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@29x29.png diff --git a/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@40x40-1.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@40x40-1.png similarity index 100% rename from Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@40x40-1.png rename to Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@40x40-1.png diff --git a/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@58x58-1.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@58x58-1.png similarity index 100% rename from Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@58x58-1.png rename to Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@58x58-1.png diff --git a/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@58x58.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@58x58.png similarity index 100% rename from Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@58x58.png rename to Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@58x58.png diff --git a/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@80x80-1.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@80x80-1.png similarity index 100% rename from Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@80x80-1.png rename to Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@80x80-1.png diff --git a/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@80x80.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@80x80.png similarity index 100% rename from Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@80x80.png rename to Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@80x80.png diff --git a/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@87x87.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@87x87.png similarity index 100% rename from Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@87x87.png rename to Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@87x87.png diff --git a/Telegram-iOS/Icons.xcassets/Contents.json b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/Contents.json similarity index 100% rename from Telegram-iOS/Icons.xcassets/Contents.json rename to Telegram/Telegram-iOS/DefaultAppIcon.xcassets/Contents.json diff --git a/Telegram-iOS/Icons.xcassets/AppIcon.appiconset/Contents.json b/Telegram/Telegram-iOS/Icons.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from Telegram-iOS/Icons.xcassets/AppIcon.appiconset/Contents.json rename to Telegram/Telegram-iOS/Icons.xcassets/AppIcon.appiconset/Contents.json diff --git a/Telegram-iOS/Icons.xcassets/AppIcon.appiconset/icon@1024px.png b/Telegram/Telegram-iOS/Icons.xcassets/AppIcon.appiconset/icon@1024px.png similarity index 100% rename from Telegram-iOS/Icons.xcassets/AppIcon.appiconset/icon@1024px.png rename to Telegram/Telegram-iOS/Icons.xcassets/AppIcon.appiconset/icon@1024px.png diff --git a/Telegram-iOS/Icons.xcassets/AppIcon.appiconset/icon@120px.png b/Telegram/Telegram-iOS/Icons.xcassets/AppIcon.appiconset/icon@120px.png similarity index 100% rename from Telegram-iOS/Icons.xcassets/AppIcon.appiconset/icon@120px.png rename to Telegram/Telegram-iOS/Icons.xcassets/AppIcon.appiconset/icon@120px.png diff --git a/Telegram-iOS/Icons.xcassets/AppIcon.appiconset/icon@152px.png b/Telegram/Telegram-iOS/Icons.xcassets/AppIcon.appiconset/icon@152px.png similarity index 100% rename from Telegram-iOS/Icons.xcassets/AppIcon.appiconset/icon@152px.png rename to Telegram/Telegram-iOS/Icons.xcassets/AppIcon.appiconset/icon@152px.png diff --git a/Telegram-iOS/Icons.xcassets/AppIcon.appiconset/icon@167px.png b/Telegram/Telegram-iOS/Icons.xcassets/AppIcon.appiconset/icon@167px.png similarity index 100% rename from Telegram-iOS/Icons.xcassets/AppIcon.appiconset/icon@167px.png rename to Telegram/Telegram-iOS/Icons.xcassets/AppIcon.appiconset/icon@167px.png diff --git a/Telegram-iOS/Icons.xcassets/AppIcon.appiconset/icon@180px.png b/Telegram/Telegram-iOS/Icons.xcassets/AppIcon.appiconset/icon@180px.png similarity index 100% rename from Telegram-iOS/Icons.xcassets/AppIcon.appiconset/icon@180px.png rename to Telegram/Telegram-iOS/Icons.xcassets/AppIcon.appiconset/icon@180px.png diff --git a/Telegram-iOS/Icons.xcassets/AppIcon.appiconset/icon@76px.png b/Telegram/Telegram-iOS/Icons.xcassets/AppIcon.appiconset/icon@76px.png similarity index 100% rename from Telegram-iOS/Icons.xcassets/AppIcon.appiconset/icon@76px.png rename to Telegram/Telegram-iOS/Icons.xcassets/AppIcon.appiconset/icon@76px.png diff --git a/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIcon@2x-1.png b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIcon@2x-1.png new file mode 100644 index 0000000000..dd360d8f50 Binary files /dev/null and b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIcon@2x-1.png differ diff --git a/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIcon@2x.png b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIcon@2x.png new file mode 100644 index 0000000000..2e502e7dab Binary files /dev/null and b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIcon@2x.png differ diff --git a/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIcon@3x.png b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIcon@3x.png new file mode 100644 index 0000000000..c47aeed4b1 Binary files /dev/null and b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIcon@3x.png differ diff --git a/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIconIpad.png b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIconIpad.png new file mode 100644 index 0000000000..5eaf0bab23 Binary files /dev/null and b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIconIpad.png differ diff --git a/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIconIpad@2x.png b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIconIpad@2x.png new file mode 100644 index 0000000000..6d9e7ab98c Binary files /dev/null and b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIconIpad@2x.png differ diff --git a/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIconLargeIpad@2x.png b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIconLargeIpad@2x.png new file mode 100644 index 0000000000..9bf363744d Binary files /dev/null and b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueIconLargeIpad@2x.png differ diff --git a/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon.png b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon.png new file mode 100644 index 0000000000..dc5916282e Binary files /dev/null and b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon.png differ diff --git a/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@2x-1.png b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@2x-1.png new file mode 100644 index 0000000000..0898af42d9 Binary files /dev/null and b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@2x-1.png differ diff --git a/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@2x.png b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@2x.png new file mode 100644 index 0000000000..0898af42d9 Binary files /dev/null and b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@2x.png differ diff --git a/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@3x.png b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@3x.png new file mode 100644 index 0000000000..f7725e9914 Binary files /dev/null and b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@3x.png differ diff --git a/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Contents.json b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Contents.json new file mode 100644 index 0000000000..00dd28927f --- /dev/null +++ b/Telegram/Telegram-iOS/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/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple-iTunesArtwork.png b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple-iTunesArtwork.png new file mode 100644 index 0000000000..f00a2857f0 Binary files /dev/null and b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple-iTunesArtwork.png differ diff --git a/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@29x29.png b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@29x29.png new file mode 100644 index 0000000000..90d7b67bc0 Binary files /dev/null and b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@29x29.png differ diff --git a/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@40x40-1.png b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@40x40-1.png new file mode 100644 index 0000000000..a79cb5dcdc Binary files /dev/null and b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@40x40-1.png differ diff --git a/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@58x58-1.png b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@58x58-1.png new file mode 100644 index 0000000000..aa6a4a442e Binary files /dev/null and b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@58x58-1.png differ diff --git a/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@58x58.png b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@58x58.png new file mode 100644 index 0000000000..aa6a4a442e Binary files /dev/null and b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@58x58.png differ diff --git a/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@80x80-1.png b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@80x80-1.png new file mode 100644 index 0000000000..385bc474b2 Binary files /dev/null and b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@80x80-1.png differ diff --git a/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@80x80.png b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@80x80.png new file mode 100644 index 0000000000..385bc474b2 Binary files /dev/null and b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@80x80.png differ diff --git a/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@87x87.png b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@87x87.png new file mode 100644 index 0000000000..c0a9ce9319 Binary files /dev/null and b/Telegram/Telegram-iOS/Icons.xcassets/AppIconLLC.appiconset/Simple@87x87.png differ diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/Contents.json b/Telegram/Telegram-iOS/Icons.xcassets/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/Contents.json rename to Telegram/Telegram-iOS/Icons.xcassets/Contents.json diff --git a/Telegram-iOS/Icons.xcassets/Shortcuts/Account.imageset/Contents.json b/Telegram/Telegram-iOS/Icons.xcassets/Shortcuts/Account.imageset/Contents.json similarity index 100% rename from Telegram-iOS/Icons.xcassets/Shortcuts/Account.imageset/Contents.json rename to Telegram/Telegram-iOS/Icons.xcassets/Shortcuts/Account.imageset/Contents.json diff --git a/Telegram-iOS/Icons.xcassets/Shortcuts/Account.imageset/ic_lt_user.pdf b/Telegram/Telegram-iOS/Icons.xcassets/Shortcuts/Account.imageset/ic_lt_user.pdf similarity index 100% rename from Telegram-iOS/Icons.xcassets/Shortcuts/Account.imageset/ic_lt_user.pdf rename to Telegram/Telegram-iOS/Icons.xcassets/Shortcuts/Account.imageset/ic_lt_user.pdf diff --git a/Telegram-iOS/Icons.xcassets/Shortcuts/Camera.imageset/Contents.json b/Telegram/Telegram-iOS/Icons.xcassets/Shortcuts/Camera.imageset/Contents.json similarity index 100% rename from Telegram-iOS/Icons.xcassets/Shortcuts/Camera.imageset/Contents.json rename to Telegram/Telegram-iOS/Icons.xcassets/Shortcuts/Camera.imageset/Contents.json diff --git a/Telegram-iOS/Icons.xcassets/Shortcuts/Camera.imageset/ic_camera.pdf b/Telegram/Telegram-iOS/Icons.xcassets/Shortcuts/Camera.imageset/ic_camera.pdf similarity index 100% rename from Telegram-iOS/Icons.xcassets/Shortcuts/Camera.imageset/ic_camera.pdf rename to Telegram/Telegram-iOS/Icons.xcassets/Shortcuts/Camera.imageset/ic_camera.pdf diff --git a/Telegram-iOS/Icons.xcassets/Shortcuts/Contents.json b/Telegram/Telegram-iOS/Icons.xcassets/Shortcuts/Contents.json similarity index 100% rename from Telegram-iOS/Icons.xcassets/Shortcuts/Contents.json rename to Telegram/Telegram-iOS/Icons.xcassets/Shortcuts/Contents.json diff --git a/Telegram-iOS/Icons.xcassets/Shortcuts/SavedMessages.imageset/Contents.json b/Telegram/Telegram-iOS/Icons.xcassets/Shortcuts/SavedMessages.imageset/Contents.json similarity index 100% rename from Telegram-iOS/Icons.xcassets/Shortcuts/SavedMessages.imageset/Contents.json rename to Telegram/Telegram-iOS/Icons.xcassets/Shortcuts/SavedMessages.imageset/Contents.json diff --git a/Telegram-iOS/Icons.xcassets/Shortcuts/SavedMessages.imageset/ic_savedmessages.pdf b/Telegram/Telegram-iOS/Icons.xcassets/Shortcuts/SavedMessages.imageset/ic_savedmessages.pdf similarity index 100% rename from Telegram-iOS/Icons.xcassets/Shortcuts/SavedMessages.imageset/ic_savedmessages.pdf rename to Telegram/Telegram-iOS/Icons.xcassets/Shortcuts/SavedMessages.imageset/ic_savedmessages.pdf diff --git a/Telegram-iOS/Info.plist b/Telegram/Telegram-iOS/Info.plist similarity index 100% rename from Telegram-iOS/Info.plist rename to Telegram/Telegram-iOS/Info.plist diff --git a/Telegram/Telegram-iOS/InfoBazel.plist b/Telegram/Telegram-iOS/InfoBazel.plist new file mode 100644 index 0000000000..e7ca119cd8 --- /dev/null +++ b/Telegram/Telegram-iOS/InfoBazel.plist @@ -0,0 +1,207 @@ + + + + + BGTaskSchedulerPermittedIdentifiers + + $(PRODUCT_BUNDLE_IDENTIFIER).refresh + + CFBundleAllowMixedLocalizations + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + ${APP_NAME} + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(PRODUCT_BUNDLE_SHORT_VERSION) + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleTypeRole + Viewer + CFBundleURLName + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleURLSchemes + + telegram + + + + CFBundleTypeRole + Viewer + CFBundleURLName + $(PRODUCT_BUNDLE_IDENTIFIER).compatibility + CFBundleURLSchemes + + tg + $(APP_SPECIFIC_URL_SCHEME) + + + + CFBundleTypeRole + Viewer + CFBundleURLName + $(PRODUCT_BUNDLE_IDENTIFIER).ton + CFBundleURLSchemes + + ton + + + + CFBundleVersion + ${BUILD_NUMBER} + ITSAppUsesNonExemptEncryption + + LSApplicationQueriesSchemes + + instagram + comgooglemaps-x-callback + foursquare + here-location + yandexmaps + yandexnavi + comgooglemaps + youtube + twitter + vk + waze + googlechrome + firefox + touch-http + yandexbrowser-open-url + vimeo + vine + coub + uber + citymapper + lyft + opera-http + firefox-focus + ddgQuickLink + moovit + alook + dgis + microsoft-edge-http + brave + onionhttp + ucbrowser + dolphin + + LSRequiresIPhoneOS + + NSAppTransportSecurity + + 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 + + UIDeviceFamily + + 1 + 2 + + UIFileSharingEnabled + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UIRequiresPersistentWiFi + + UIStatusBarStyle + UIStatusBarStyleDefault + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + UIViewEdgeAntialiasing + + 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/Telegram-iOS/Resources/Compass.tgs b/Telegram/Telegram-iOS/Resources/Compass.tgs similarity index 100% rename from Telegram-iOS/Resources/Compass.tgs rename to Telegram/Telegram-iOS/Resources/Compass.tgs diff --git a/Telegram-iOS/Resources/Dice_Rolling.tgs b/Telegram/Telegram-iOS/Resources/Dice_Rolling.tgs similarity index 100% rename from Telegram-iOS/Resources/Dice_Rolling.tgs rename to Telegram/Telegram-iOS/Resources/Dice_Rolling.tgs diff --git a/Telegram-iOS/Resources/NavigationBackArrowLight@2x.png b/Telegram/Telegram-iOS/Resources/NavigationBackArrowLight@2x.png similarity index 100% rename from Telegram-iOS/Resources/NavigationBackArrowLight@2x.png rename to Telegram/Telegram-iOS/Resources/NavigationBackArrowLight@2x.png diff --git a/Telegram-iOS/Resources/NavigationShadow@2x.png b/Telegram/Telegram-iOS/Resources/NavigationShadow@2x.png similarity index 100% rename from Telegram-iOS/Resources/NavigationShadow@2x.png rename to Telegram/Telegram-iOS/Resources/NavigationShadow@2x.png diff --git a/Telegram-iOS/Resources/PhotoEditor/PhotoEditorCaption@2x.png b/Telegram/Telegram-iOS/Resources/PhotoEditor/PhotoEditorCaption@2x.png similarity index 100% rename from Telegram-iOS/Resources/PhotoEditor/PhotoEditorCaption@2x.png rename to Telegram/Telegram-iOS/Resources/PhotoEditor/PhotoEditorCaption@2x.png diff --git a/Telegram-iOS/Resources/PhotoEditor/PhotoEditorCaption@3x.png b/Telegram/Telegram-iOS/Resources/PhotoEditor/PhotoEditorCaption@3x.png similarity index 100% rename from Telegram-iOS/Resources/PhotoEditor/PhotoEditorCaption@3x.png rename to Telegram/Telegram-iOS/Resources/PhotoEditor/PhotoEditorCaption@3x.png diff --git a/Telegram-iOS/Resources/PhotoEditor/PhotoEditorMute@2x.png b/Telegram/Telegram-iOS/Resources/PhotoEditor/PhotoEditorMute@2x.png similarity index 100% rename from Telegram-iOS/Resources/PhotoEditor/PhotoEditorMute@2x.png rename to Telegram/Telegram-iOS/Resources/PhotoEditor/PhotoEditorMute@2x.png diff --git a/Telegram-iOS/Resources/PhotoEditor/PhotoEditorMuteActive@2x.png b/Telegram/Telegram-iOS/Resources/PhotoEditor/PhotoEditorMuteActive@2x.png similarity index 100% rename from Telegram-iOS/Resources/PhotoEditor/PhotoEditorMuteActive@2x.png rename to Telegram/Telegram-iOS/Resources/PhotoEditor/PhotoEditorMuteActive@2x.png diff --git a/Telegram-iOS/Resources/begin_record.caf b/Telegram/Telegram-iOS/Resources/begin_record.caf similarity index 100% rename from Telegram-iOS/Resources/begin_record.caf rename to Telegram/Telegram-iOS/Resources/begin_record.caf diff --git a/Telegram-iOS/Resources/intro/fast_arrow@2x.png b/Telegram/Telegram-iOS/Resources/intro/fast_arrow@2x.png similarity index 100% rename from Telegram-iOS/Resources/intro/fast_arrow@2x.png rename to Telegram/Telegram-iOS/Resources/intro/fast_arrow@2x.png diff --git a/Telegram-iOS/Resources/intro/fast_arrow_shadow@2x.png b/Telegram/Telegram-iOS/Resources/intro/fast_arrow_shadow@2x.png similarity index 100% rename from Telegram-iOS/Resources/intro/fast_arrow_shadow@2x.png rename to Telegram/Telegram-iOS/Resources/intro/fast_arrow_shadow@2x.png diff --git a/Telegram-iOS/Resources/intro/fast_body@2x.png b/Telegram/Telegram-iOS/Resources/intro/fast_body@2x.png similarity index 100% rename from Telegram-iOS/Resources/intro/fast_body@2x.png rename to Telegram/Telegram-iOS/Resources/intro/fast_body@2x.png diff --git a/Telegram-iOS/Resources/intro/fast_spiral@2x.png b/Telegram/Telegram-iOS/Resources/intro/fast_spiral@2x.png similarity index 100% rename from Telegram-iOS/Resources/intro/fast_spiral@2x.png rename to Telegram/Telegram-iOS/Resources/intro/fast_spiral@2x.png diff --git a/Telegram-iOS/Resources/intro/ic_bubble@2x.png b/Telegram/Telegram-iOS/Resources/intro/ic_bubble@2x.png similarity index 100% rename from Telegram-iOS/Resources/intro/ic_bubble@2x.png rename to Telegram/Telegram-iOS/Resources/intro/ic_bubble@2x.png diff --git a/Telegram-iOS/Resources/intro/ic_bubble_dot@2x.png b/Telegram/Telegram-iOS/Resources/intro/ic_bubble_dot@2x.png similarity index 100% rename from Telegram-iOS/Resources/intro/ic_bubble_dot@2x.png rename to Telegram/Telegram-iOS/Resources/intro/ic_bubble_dot@2x.png diff --git a/Telegram-iOS/Resources/intro/ic_cam@2x.png b/Telegram/Telegram-iOS/Resources/intro/ic_cam@2x.png similarity index 100% rename from Telegram-iOS/Resources/intro/ic_cam@2x.png rename to Telegram/Telegram-iOS/Resources/intro/ic_cam@2x.png diff --git a/Telegram-iOS/Resources/intro/ic_cam_lens@2x.png b/Telegram/Telegram-iOS/Resources/intro/ic_cam_lens@2x.png similarity index 100% rename from Telegram-iOS/Resources/intro/ic_cam_lens@2x.png rename to Telegram/Telegram-iOS/Resources/intro/ic_cam_lens@2x.png diff --git a/Telegram-iOS/Resources/intro/ic_pencil@2x.png b/Telegram/Telegram-iOS/Resources/intro/ic_pencil@2x.png similarity index 100% rename from Telegram-iOS/Resources/intro/ic_pencil@2x.png rename to Telegram/Telegram-iOS/Resources/intro/ic_pencil@2x.png diff --git a/Telegram-iOS/Resources/intro/ic_pin@2x.png b/Telegram/Telegram-iOS/Resources/intro/ic_pin@2x.png similarity index 100% rename from Telegram-iOS/Resources/intro/ic_pin@2x.png rename to Telegram/Telegram-iOS/Resources/intro/ic_pin@2x.png diff --git a/Telegram-iOS/Resources/intro/ic_smile@2x.png b/Telegram/Telegram-iOS/Resources/intro/ic_smile@2x.png similarity index 100% rename from Telegram-iOS/Resources/intro/ic_smile@2x.png rename to Telegram/Telegram-iOS/Resources/intro/ic_smile@2x.png diff --git a/Telegram-iOS/Resources/intro/ic_smile_eye@2x.png b/Telegram/Telegram-iOS/Resources/intro/ic_smile_eye@2x.png similarity index 100% rename from Telegram-iOS/Resources/intro/ic_smile_eye@2x.png rename to Telegram/Telegram-iOS/Resources/intro/ic_smile_eye@2x.png diff --git a/Telegram-iOS/Resources/intro/ic_videocam@2x.png b/Telegram/Telegram-iOS/Resources/intro/ic_videocam@2x.png similarity index 100% rename from Telegram-iOS/Resources/intro/ic_videocam@2x.png rename to Telegram/Telegram-iOS/Resources/intro/ic_videocam@2x.png diff --git a/Telegram-iOS/Resources/intro/knot_down@2x.png b/Telegram/Telegram-iOS/Resources/intro/knot_down@2x.png similarity index 100% rename from Telegram-iOS/Resources/intro/knot_down@2x.png rename to Telegram/Telegram-iOS/Resources/intro/knot_down@2x.png diff --git a/Telegram-iOS/Resources/intro/knot_up1@2x.png b/Telegram/Telegram-iOS/Resources/intro/knot_up1@2x.png similarity index 100% rename from Telegram-iOS/Resources/intro/knot_up1@2x.png rename to Telegram/Telegram-iOS/Resources/intro/knot_up1@2x.png diff --git a/Telegram-iOS/Resources/intro/powerful_infinity@2x.png b/Telegram/Telegram-iOS/Resources/intro/powerful_infinity@2x.png similarity index 100% rename from Telegram-iOS/Resources/intro/powerful_infinity@2x.png rename to Telegram/Telegram-iOS/Resources/intro/powerful_infinity@2x.png diff --git a/Telegram-iOS/Resources/intro/powerful_infinity_white@2x.png b/Telegram/Telegram-iOS/Resources/intro/powerful_infinity_white@2x.png similarity index 100% rename from Telegram-iOS/Resources/intro/powerful_infinity_white@2x.png rename to Telegram/Telegram-iOS/Resources/intro/powerful_infinity_white@2x.png diff --git a/Telegram-iOS/Resources/intro/powerful_mask@2x.png b/Telegram/Telegram-iOS/Resources/intro/powerful_mask@2x.png similarity index 100% rename from Telegram-iOS/Resources/intro/powerful_mask@2x.png rename to Telegram/Telegram-iOS/Resources/intro/powerful_mask@2x.png diff --git a/Telegram-iOS/Resources/intro/powerful_star@2x.png b/Telegram/Telegram-iOS/Resources/intro/powerful_star@2x.png similarity index 100% rename from Telegram-iOS/Resources/intro/powerful_star@2x.png rename to Telegram/Telegram-iOS/Resources/intro/powerful_star@2x.png diff --git a/Telegram-iOS/Resources/intro/private_door@2x.png b/Telegram/Telegram-iOS/Resources/intro/private_door@2x.png similarity index 100% rename from Telegram-iOS/Resources/intro/private_door@2x.png rename to Telegram/Telegram-iOS/Resources/intro/private_door@2x.png diff --git a/Telegram-iOS/Resources/intro/private_screw@2x.png b/Telegram/Telegram-iOS/Resources/intro/private_screw@2x.png similarity index 100% rename from Telegram-iOS/Resources/intro/private_screw@2x.png rename to Telegram/Telegram-iOS/Resources/intro/private_screw@2x.png diff --git a/Telegram-iOS/Resources/intro/start_arrow@2x.png b/Telegram/Telegram-iOS/Resources/intro/start_arrow@2x.png similarity index 100% rename from Telegram-iOS/Resources/intro/start_arrow@2x.png rename to Telegram/Telegram-iOS/Resources/intro/start_arrow@2x.png diff --git a/Telegram-iOS/Resources/intro/start_arrow_ipad.png b/Telegram/Telegram-iOS/Resources/intro/start_arrow_ipad.png similarity index 100% rename from Telegram-iOS/Resources/intro/start_arrow_ipad.png rename to Telegram/Telegram-iOS/Resources/intro/start_arrow_ipad.png diff --git a/Telegram-iOS/Resources/intro/start_arrow_ipad@2x.png b/Telegram/Telegram-iOS/Resources/intro/start_arrow_ipad@2x.png similarity index 100% rename from Telegram-iOS/Resources/intro/start_arrow_ipad@2x.png rename to Telegram/Telegram-iOS/Resources/intro/start_arrow_ipad@2x.png diff --git a/Telegram-iOS/Resources/intro/telegram_plane1@2x.png b/Telegram/Telegram-iOS/Resources/intro/telegram_plane1@2x.png similarity index 100% rename from Telegram-iOS/Resources/intro/telegram_plane1@2x.png rename to Telegram/Telegram-iOS/Resources/intro/telegram_plane1@2x.png diff --git a/Telegram-iOS/Resources/intro/telegram_sphere@2x.png b/Telegram/Telegram-iOS/Resources/intro/telegram_sphere@2x.png similarity index 100% rename from Telegram-iOS/Resources/intro/telegram_sphere@2x.png rename to Telegram/Telegram-iOS/Resources/intro/telegram_sphere@2x.png diff --git a/Telegram-iOS/Resources/notifications/0.m4a b/Telegram/Telegram-iOS/Resources/notifications/0.m4a similarity index 100% rename from Telegram-iOS/Resources/notifications/0.m4a rename to Telegram/Telegram-iOS/Resources/notifications/0.m4a diff --git a/Telegram-iOS/Resources/notifications/1.m4a b/Telegram/Telegram-iOS/Resources/notifications/1.m4a similarity index 100% rename from Telegram-iOS/Resources/notifications/1.m4a rename to Telegram/Telegram-iOS/Resources/notifications/1.m4a diff --git a/Telegram-iOS/Resources/notifications/100.m4a b/Telegram/Telegram-iOS/Resources/notifications/100.m4a similarity index 100% rename from Telegram-iOS/Resources/notifications/100.m4a rename to Telegram/Telegram-iOS/Resources/notifications/100.m4a diff --git a/Telegram-iOS/Resources/notifications/101.m4a b/Telegram/Telegram-iOS/Resources/notifications/101.m4a similarity index 100% rename from Telegram-iOS/Resources/notifications/101.m4a rename to Telegram/Telegram-iOS/Resources/notifications/101.m4a diff --git a/Telegram-iOS/Resources/notifications/102.m4a b/Telegram/Telegram-iOS/Resources/notifications/102.m4a similarity index 100% rename from Telegram-iOS/Resources/notifications/102.m4a rename to Telegram/Telegram-iOS/Resources/notifications/102.m4a diff --git a/Telegram-iOS/Resources/notifications/103.m4a b/Telegram/Telegram-iOS/Resources/notifications/103.m4a similarity index 100% rename from Telegram-iOS/Resources/notifications/103.m4a rename to Telegram/Telegram-iOS/Resources/notifications/103.m4a diff --git a/Telegram-iOS/Resources/notifications/104.m4a b/Telegram/Telegram-iOS/Resources/notifications/104.m4a similarity index 100% rename from Telegram-iOS/Resources/notifications/104.m4a rename to Telegram/Telegram-iOS/Resources/notifications/104.m4a diff --git a/Telegram-iOS/Resources/notifications/105.m4a b/Telegram/Telegram-iOS/Resources/notifications/105.m4a similarity index 100% rename from Telegram-iOS/Resources/notifications/105.m4a rename to Telegram/Telegram-iOS/Resources/notifications/105.m4a diff --git a/Telegram-iOS/Resources/notifications/106.m4a b/Telegram/Telegram-iOS/Resources/notifications/106.m4a similarity index 100% rename from Telegram-iOS/Resources/notifications/106.m4a rename to Telegram/Telegram-iOS/Resources/notifications/106.m4a diff --git a/Telegram-iOS/Resources/notifications/107.m4a b/Telegram/Telegram-iOS/Resources/notifications/107.m4a similarity index 100% rename from Telegram-iOS/Resources/notifications/107.m4a rename to Telegram/Telegram-iOS/Resources/notifications/107.m4a diff --git a/Telegram-iOS/Resources/notifications/108.m4a b/Telegram/Telegram-iOS/Resources/notifications/108.m4a similarity index 100% rename from Telegram-iOS/Resources/notifications/108.m4a rename to Telegram/Telegram-iOS/Resources/notifications/108.m4a diff --git a/Telegram-iOS/Resources/notifications/109.m4a b/Telegram/Telegram-iOS/Resources/notifications/109.m4a similarity index 100% rename from Telegram-iOS/Resources/notifications/109.m4a rename to Telegram/Telegram-iOS/Resources/notifications/109.m4a diff --git a/Telegram-iOS/Resources/notifications/110.m4a b/Telegram/Telegram-iOS/Resources/notifications/110.m4a similarity index 100% rename from Telegram-iOS/Resources/notifications/110.m4a rename to Telegram/Telegram-iOS/Resources/notifications/110.m4a diff --git a/Telegram-iOS/Resources/notifications/111.m4a b/Telegram/Telegram-iOS/Resources/notifications/111.m4a similarity index 100% rename from Telegram-iOS/Resources/notifications/111.m4a rename to Telegram/Telegram-iOS/Resources/notifications/111.m4a diff --git a/Telegram-iOS/Resources/notifications/2.m4a b/Telegram/Telegram-iOS/Resources/notifications/2.m4a similarity index 100% rename from Telegram-iOS/Resources/notifications/2.m4a rename to Telegram/Telegram-iOS/Resources/notifications/2.m4a diff --git a/Telegram-iOS/Resources/notifications/3.m4a b/Telegram/Telegram-iOS/Resources/notifications/3.m4a similarity index 100% rename from Telegram-iOS/Resources/notifications/3.m4a rename to Telegram/Telegram-iOS/Resources/notifications/3.m4a diff --git a/Telegram-iOS/Resources/notifications/4.m4a b/Telegram/Telegram-iOS/Resources/notifications/4.m4a similarity index 100% rename from Telegram-iOS/Resources/notifications/4.m4a rename to Telegram/Telegram-iOS/Resources/notifications/4.m4a diff --git a/Telegram-iOS/Resources/notifications/5.m4a b/Telegram/Telegram-iOS/Resources/notifications/5.m4a similarity index 100% rename from Telegram-iOS/Resources/notifications/5.m4a rename to Telegram/Telegram-iOS/Resources/notifications/5.m4a diff --git a/Telegram-iOS/Resources/notifications/6.m4a b/Telegram/Telegram-iOS/Resources/notifications/6.m4a similarity index 100% rename from Telegram-iOS/Resources/notifications/6.m4a rename to Telegram/Telegram-iOS/Resources/notifications/6.m4a diff --git a/Telegram-iOS/Resources/notifications/7.m4a b/Telegram/Telegram-iOS/Resources/notifications/7.m4a similarity index 100% rename from Telegram-iOS/Resources/notifications/7.m4a rename to Telegram/Telegram-iOS/Resources/notifications/7.m4a diff --git a/Telegram-iOS/Resources/notifications/8.m4a b/Telegram/Telegram-iOS/Resources/notifications/8.m4a similarity index 100% rename from Telegram-iOS/Resources/notifications/8.m4a rename to Telegram/Telegram-iOS/Resources/notifications/8.m4a diff --git a/Telegram-iOS/Resources/notifications/9.m4a b/Telegram/Telegram-iOS/Resources/notifications/9.m4a similarity index 100% rename from Telegram-iOS/Resources/notifications/9.m4a rename to Telegram/Telegram-iOS/Resources/notifications/9.m4a diff --git a/Telegram-iOS/Resources/voip_busy.caf b/Telegram/Telegram-iOS/Resources/voip_busy.caf similarity index 100% rename from Telegram-iOS/Resources/voip_busy.caf rename to Telegram/Telegram-iOS/Resources/voip_busy.caf diff --git a/Telegram-iOS/Resources/voip_connecting.mp3 b/Telegram/Telegram-iOS/Resources/voip_connecting.mp3 similarity index 100% rename from Telegram-iOS/Resources/voip_connecting.mp3 rename to Telegram/Telegram-iOS/Resources/voip_connecting.mp3 diff --git a/Telegram-iOS/Resources/voip_end.caf b/Telegram/Telegram-iOS/Resources/voip_end.caf similarity index 100% rename from Telegram-iOS/Resources/voip_end.caf rename to Telegram/Telegram-iOS/Resources/voip_end.caf diff --git a/Telegram-iOS/Resources/voip_fail.caf b/Telegram/Telegram-iOS/Resources/voip_fail.caf similarity index 100% rename from Telegram-iOS/Resources/voip_fail.caf rename to Telegram/Telegram-iOS/Resources/voip_fail.caf diff --git a/Telegram-iOS/Resources/voip_ringback.caf b/Telegram/Telegram-iOS/Resources/voip_ringback.caf similarity index 100% rename from Telegram-iOS/Resources/voip_ringback.caf rename to Telegram/Telegram-iOS/Resources/voip_ringback.caf diff --git a/Telegram-iOS/Telegram-Bridging-Header.h b/Telegram/Telegram-iOS/Telegram-Bridging-Header.h similarity index 100% rename from Telegram-iOS/Telegram-Bridging-Header.h rename to Telegram/Telegram-iOS/Telegram-Bridging-Header.h diff --git a/Telegram-iOS/WhiteFilledIcon@2x.png b/Telegram/Telegram-iOS/WhiteFilledIcon@2x.png similarity index 100% rename from Telegram-iOS/WhiteFilledIcon@2x.png rename to Telegram/Telegram-iOS/WhiteFilledIcon@2x.png diff --git a/Telegram-iOS/WhiteFilledIcon@3x.png b/Telegram/Telegram-iOS/WhiteFilledIcon@3x.png similarity index 100% rename from Telegram-iOS/WhiteFilledIcon@3x.png rename to Telegram/Telegram-iOS/WhiteFilledIcon@3x.png diff --git a/Telegram-iOS/ar.lproj/AppIntentVocabulary.plist b/Telegram/Telegram-iOS/ar.lproj/AppIntentVocabulary.plist similarity index 100% rename from Telegram-iOS/ar.lproj/AppIntentVocabulary.plist rename to Telegram/Telegram-iOS/ar.lproj/AppIntentVocabulary.plist diff --git a/Telegram-iOS/ar.lproj/InfoPlist.strings b/Telegram/Telegram-iOS/ar.lproj/InfoPlist.strings similarity index 100% rename from Telegram-iOS/ar.lproj/InfoPlist.strings rename to Telegram/Telegram-iOS/ar.lproj/InfoPlist.strings diff --git a/Telegram-iOS/de.lproj/Localizable.strings b/Telegram/Telegram-iOS/ar.lproj/Localizable.strings similarity index 100% rename from Telegram-iOS/de.lproj/Localizable.strings rename to Telegram/Telegram-iOS/ar.lproj/Localizable.strings diff --git a/Telegram-iOS/ca.lproj/AppIntentVocabulary.plist b/Telegram/Telegram-iOS/ca.lproj/AppIntentVocabulary.plist similarity index 100% rename from Telegram-iOS/ca.lproj/AppIntentVocabulary.plist rename to Telegram/Telegram-iOS/ca.lproj/AppIntentVocabulary.plist diff --git a/Telegram-iOS/ca.lproj/InfoPlist.strings b/Telegram/Telegram-iOS/ca.lproj/InfoPlist.strings similarity index 100% rename from Telegram-iOS/ca.lproj/InfoPlist.strings rename to Telegram/Telegram-iOS/ca.lproj/InfoPlist.strings diff --git a/Telegram-iOS/ca.lproj/Localizable.strings b/Telegram/Telegram-iOS/ca.lproj/Localizable.strings similarity index 100% rename from Telegram-iOS/ca.lproj/Localizable.strings rename to Telegram/Telegram-iOS/ca.lproj/Localizable.strings diff --git a/Telegram-iOS/de.lproj/AppIntentVocabulary.plist b/Telegram/Telegram-iOS/de.lproj/AppIntentVocabulary.plist similarity index 100% rename from Telegram-iOS/de.lproj/AppIntentVocabulary.plist rename to Telegram/Telegram-iOS/de.lproj/AppIntentVocabulary.plist diff --git a/Telegram-iOS/de.lproj/InfoPlist.strings b/Telegram/Telegram-iOS/de.lproj/InfoPlist.strings similarity index 100% rename from Telegram-iOS/de.lproj/InfoPlist.strings rename to Telegram/Telegram-iOS/de.lproj/InfoPlist.strings diff --git a/Telegram-iOS/es.lproj/Localizable.strings b/Telegram/Telegram-iOS/de.lproj/Localizable.strings similarity index 100% rename from Telegram-iOS/es.lproj/Localizable.strings rename to Telegram/Telegram-iOS/de.lproj/Localizable.strings diff --git a/Telegram-iOS/en.lproj/AppIntentVocabulary.plist b/Telegram/Telegram-iOS/en.lproj/AppIntentVocabulary.plist similarity index 100% rename from Telegram-iOS/en.lproj/AppIntentVocabulary.plist rename to Telegram/Telegram-iOS/en.lproj/AppIntentVocabulary.plist diff --git a/Telegram-iOS/en.lproj/InfoPlist.strings b/Telegram/Telegram-iOS/en.lproj/InfoPlist.strings similarity index 100% rename from Telegram-iOS/en.lproj/InfoPlist.strings rename to Telegram/Telegram-iOS/en.lproj/InfoPlist.strings diff --git a/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings similarity index 99% rename from Telegram-iOS/en.lproj/Localizable.strings rename to Telegram/Telegram-iOS/en.lproj/Localizable.strings index 710bcd7c41..f2c0148559 100644 --- a/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -4858,6 +4858,7 @@ Any member of this group will be able to see messages in the channel."; "Wallet.Send.OwnAddressAlertProceed" = "Proceed"; "Wallet.Send.TransactionInProgress" = "Please wait until the current transaction is completed."; "Wallet.Send.SyncInProgress" = "Please wait while the wallet finishes syncing with the TON Blockchain."; +"Wallet.Send.EncryptComment" = "Encrypt Text"; "Wallet.Settings.Title" = "Settings"; "Wallet.Settings.Configuration" = "Server Settings"; "Wallet.Settings.ConfigurationInfo" = "Advanced Settings"; @@ -5351,7 +5352,9 @@ Any member of this group will be able to see messages in the channel."; "ChatList.EmptyChatFilterList" = "No chats currently\nmatch this filter."; "ChatList.EmptyChatListNewMessage" = "New Message"; -"ChatList.EmptyChatListEditFilter" = "Edit Filter"; +"ChatList.EmptyChatListEditFilter" = "Edit Folder"; + +"ChatListFilter.AddChatsTitle" = "Add Chats"; "Stats.Overview" = "OVERVIEW"; "Stats.Followers" = "Followers"; diff --git a/Telegram-iOS/es.lproj/AppIntentVocabulary.plist b/Telegram/Telegram-iOS/es.lproj/AppIntentVocabulary.plist similarity index 100% rename from Telegram-iOS/es.lproj/AppIntentVocabulary.plist rename to Telegram/Telegram-iOS/es.lproj/AppIntentVocabulary.plist diff --git a/Telegram-iOS/es.lproj/InfoPlist.strings b/Telegram/Telegram-iOS/es.lproj/InfoPlist.strings similarity index 100% rename from Telegram-iOS/es.lproj/InfoPlist.strings rename to Telegram/Telegram-iOS/es.lproj/InfoPlist.strings diff --git a/Telegram-iOS/it.lproj/Localizable.strings b/Telegram/Telegram-iOS/es.lproj/Localizable.strings similarity index 100% rename from Telegram-iOS/it.lproj/Localizable.strings rename to Telegram/Telegram-iOS/es.lproj/Localizable.strings diff --git a/Telegram-iOS/fr.lproj/AppIntentVocabulary.plist b/Telegram/Telegram-iOS/fr.lproj/AppIntentVocabulary.plist similarity index 100% rename from Telegram-iOS/fr.lproj/AppIntentVocabulary.plist rename to Telegram/Telegram-iOS/fr.lproj/AppIntentVocabulary.plist diff --git a/Telegram-iOS/fr.lproj/InfoPlist.strings b/Telegram/Telegram-iOS/fr.lproj/InfoPlist.strings similarity index 100% rename from Telegram-iOS/fr.lproj/InfoPlist.strings rename to Telegram/Telegram-iOS/fr.lproj/InfoPlist.strings diff --git a/Telegram-iOS/fr.lproj/Localizable.strings b/Telegram/Telegram-iOS/fr.lproj/Localizable.strings similarity index 100% rename from Telegram-iOS/fr.lproj/Localizable.strings rename to Telegram/Telegram-iOS/fr.lproj/Localizable.strings diff --git a/Telegram-iOS/id.lproj/AppIntentVocabulary.plist b/Telegram/Telegram-iOS/id.lproj/AppIntentVocabulary.plist similarity index 100% rename from Telegram-iOS/id.lproj/AppIntentVocabulary.plist rename to Telegram/Telegram-iOS/id.lproj/AppIntentVocabulary.plist diff --git a/Telegram-iOS/id.lproj/InfoPlist.strings b/Telegram/Telegram-iOS/id.lproj/InfoPlist.strings similarity index 100% rename from Telegram-iOS/id.lproj/InfoPlist.strings rename to Telegram/Telegram-iOS/id.lproj/InfoPlist.strings diff --git a/Telegram-iOS/id.lproj/Localizable.strings b/Telegram/Telegram-iOS/id.lproj/Localizable.strings similarity index 100% rename from Telegram-iOS/id.lproj/Localizable.strings rename to Telegram/Telegram-iOS/id.lproj/Localizable.strings diff --git a/Telegram-iOS/it.lproj/AppIntentVocabulary.plist b/Telegram/Telegram-iOS/it.lproj/AppIntentVocabulary.plist similarity index 100% rename from Telegram-iOS/it.lproj/AppIntentVocabulary.plist rename to Telegram/Telegram-iOS/it.lproj/AppIntentVocabulary.plist diff --git a/Telegram-iOS/it.lproj/InfoPlist.strings b/Telegram/Telegram-iOS/it.lproj/InfoPlist.strings similarity index 100% rename from Telegram-iOS/it.lproj/InfoPlist.strings rename to Telegram/Telegram-iOS/it.lproj/InfoPlist.strings diff --git a/Telegram-iOS/ko.lproj/Localizable.strings b/Telegram/Telegram-iOS/it.lproj/Localizable.strings similarity index 100% rename from Telegram-iOS/ko.lproj/Localizable.strings rename to Telegram/Telegram-iOS/it.lproj/Localizable.strings diff --git a/Telegram-iOS/ko.lproj/AppIntentVocabulary.plist b/Telegram/Telegram-iOS/ko.lproj/AppIntentVocabulary.plist similarity index 100% rename from Telegram-iOS/ko.lproj/AppIntentVocabulary.plist rename to Telegram/Telegram-iOS/ko.lproj/AppIntentVocabulary.plist diff --git a/Telegram-iOS/ko.lproj/InfoPlist.strings b/Telegram/Telegram-iOS/ko.lproj/InfoPlist.strings similarity index 100% rename from Telegram-iOS/ko.lproj/InfoPlist.strings rename to Telegram/Telegram-iOS/ko.lproj/InfoPlist.strings diff --git a/Telegram-iOS/nl.lproj/Localizable.strings b/Telegram/Telegram-iOS/ko.lproj/Localizable.strings similarity index 100% rename from Telegram-iOS/nl.lproj/Localizable.strings rename to Telegram/Telegram-iOS/ko.lproj/Localizable.strings diff --git a/Telegram-iOS/main.m b/Telegram/Telegram-iOS/main.m similarity index 100% rename from Telegram-iOS/main.m rename to Telegram/Telegram-iOS/main.m diff --git a/Telegram-iOS/ms.lproj/AppIntentVocabulary.plist b/Telegram/Telegram-iOS/ms.lproj/AppIntentVocabulary.plist similarity index 100% rename from Telegram-iOS/ms.lproj/AppIntentVocabulary.plist rename to Telegram/Telegram-iOS/ms.lproj/AppIntentVocabulary.plist diff --git a/Telegram-iOS/ms.lproj/InfoPlist.strings b/Telegram/Telegram-iOS/ms.lproj/InfoPlist.strings similarity index 100% rename from Telegram-iOS/ms.lproj/InfoPlist.strings rename to Telegram/Telegram-iOS/ms.lproj/InfoPlist.strings diff --git a/Telegram-iOS/ms.lproj/Localizable.strings b/Telegram/Telegram-iOS/ms.lproj/Localizable.strings similarity index 100% rename from Telegram-iOS/ms.lproj/Localizable.strings rename to Telegram/Telegram-iOS/ms.lproj/Localizable.strings diff --git a/Telegram-iOS/nl.lproj/AppIntentVocabulary.plist b/Telegram/Telegram-iOS/nl.lproj/AppIntentVocabulary.plist similarity index 100% rename from Telegram-iOS/nl.lproj/AppIntentVocabulary.plist rename to Telegram/Telegram-iOS/nl.lproj/AppIntentVocabulary.plist diff --git a/Telegram-iOS/nl.lproj/InfoPlist.strings b/Telegram/Telegram-iOS/nl.lproj/InfoPlist.strings similarity index 100% rename from Telegram-iOS/nl.lproj/InfoPlist.strings rename to Telegram/Telegram-iOS/nl.lproj/InfoPlist.strings diff --git a/Telegram-iOS/pt.lproj/Localizable.strings b/Telegram/Telegram-iOS/nl.lproj/Localizable.strings similarity index 100% rename from Telegram-iOS/pt.lproj/Localizable.strings rename to Telegram/Telegram-iOS/nl.lproj/Localizable.strings diff --git a/Telegram-iOS/pt.lproj/AppIntentVocabulary.plist b/Telegram/Telegram-iOS/pt.lproj/AppIntentVocabulary.plist similarity index 100% rename from Telegram-iOS/pt.lproj/AppIntentVocabulary.plist rename to Telegram/Telegram-iOS/pt.lproj/AppIntentVocabulary.plist diff --git a/Telegram-iOS/pt.lproj/InfoPlist.strings b/Telegram/Telegram-iOS/pt.lproj/InfoPlist.strings similarity index 100% rename from Telegram-iOS/pt.lproj/InfoPlist.strings rename to Telegram/Telegram-iOS/pt.lproj/InfoPlist.strings diff --git a/Telegram-iOS/ru.lproj/Localizable.strings b/Telegram/Telegram-iOS/pt.lproj/Localizable.strings similarity index 100% rename from Telegram-iOS/ru.lproj/Localizable.strings rename to Telegram/Telegram-iOS/pt.lproj/Localizable.strings diff --git a/Telegram-iOS/ru.lproj/AppIntentVocabulary.plist b/Telegram/Telegram-iOS/ru.lproj/AppIntentVocabulary.plist similarity index 100% rename from Telegram-iOS/ru.lproj/AppIntentVocabulary.plist rename to Telegram/Telegram-iOS/ru.lproj/AppIntentVocabulary.plist diff --git a/Telegram-iOS/ru.lproj/InfoPlist.strings b/Telegram/Telegram-iOS/ru.lproj/InfoPlist.strings similarity index 100% rename from Telegram-iOS/ru.lproj/InfoPlist.strings rename to Telegram/Telegram-iOS/ru.lproj/InfoPlist.strings diff --git a/submodules/Display/Display/UniversalMasterController.swift b/Telegram/Telegram-iOS/ru.lproj/Localizable.strings similarity index 100% rename from submodules/Display/Display/UniversalMasterController.swift rename to Telegram/Telegram-iOS/ru.lproj/Localizable.strings diff --git a/Telegram-iOS/tr.lproj/AppIntentVocabulary.plist b/Telegram/Telegram-iOS/tr.lproj/AppIntentVocabulary.plist similarity index 100% rename from Telegram-iOS/tr.lproj/AppIntentVocabulary.plist rename to Telegram/Telegram-iOS/tr.lproj/AppIntentVocabulary.plist diff --git a/Telegram-iOS/tr.lproj/InfoPlist.strings b/Telegram/Telegram-iOS/tr.lproj/InfoPlist.strings similarity index 100% rename from Telegram-iOS/tr.lproj/InfoPlist.strings rename to Telegram/Telegram-iOS/tr.lproj/InfoPlist.strings diff --git a/Telegram-iOS/tr.lproj/Localizable.strings b/Telegram/Telegram-iOS/tr.lproj/Localizable.strings similarity index 100% rename from Telegram-iOS/tr.lproj/Localizable.strings rename to Telegram/Telegram-iOS/tr.lproj/Localizable.strings diff --git a/Telegram-iOS/uk.lproj/AppIntentVocabulary.plist b/Telegram/Telegram-iOS/uk.lproj/AppIntentVocabulary.plist similarity index 100% rename from Telegram-iOS/uk.lproj/AppIntentVocabulary.plist rename to Telegram/Telegram-iOS/uk.lproj/AppIntentVocabulary.plist diff --git a/Telegram-iOS/uk.lproj/InfoPlist.strings b/Telegram/Telegram-iOS/uk.lproj/InfoPlist.strings similarity index 100% rename from Telegram-iOS/uk.lproj/InfoPlist.strings rename to Telegram/Telegram-iOS/uk.lproj/InfoPlist.strings diff --git a/Telegram-iOS/uk.lproj/Localizable.strings b/Telegram/Telegram-iOS/uk.lproj/Localizable.strings similarity index 100% rename from Telegram-iOS/uk.lproj/Localizable.strings rename to Telegram/Telegram-iOS/uk.lproj/Localizable.strings diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/Contents.json b/Telegram/Watch/App/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/AppIcon.appiconset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/Simple-iTunesArtwork.png b/Telegram/Watch/App/Assets.xcassets/AppIcon.appiconset/Simple-iTunesArtwork.png similarity index 100% rename from Watch/App/Assets.xcassets/AppIcon.appiconset/Simple-iTunesArtwork.png rename to Telegram/Watch/App/Assets.xcassets/AppIcon.appiconset/Simple-iTunesArtwork.png diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/Simple@58x58.png b/Telegram/Watch/App/Assets.xcassets/AppIcon.appiconset/Simple@58x58.png similarity index 100% rename from Watch/App/Assets.xcassets/AppIcon.appiconset/Simple@58x58.png rename to Telegram/Watch/App/Assets.xcassets/AppIcon.appiconset/Simple@58x58.png diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/Simple@80x80.png b/Telegram/Watch/App/Assets.xcassets/AppIcon.appiconset/Simple@80x80.png similarity index 100% rename from Watch/App/Assets.xcassets/AppIcon.appiconset/Simple@80x80.png rename to Telegram/Watch/App/Assets.xcassets/AppIcon.appiconset/Simple@80x80.png diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/Simple@87x87.png b/Telegram/Watch/App/Assets.xcassets/AppIcon.appiconset/Simple@87x87.png similarity index 100% rename from Watch/App/Assets.xcassets/AppIcon.appiconset/Simple@87x87.png rename to Telegram/Watch/App/Assets.xcassets/AppIcon.appiconset/Simple@87x87.png diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch100@2x.png b/Telegram/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch100@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/AppIcon.appiconset/Watch100@2x.png rename to Telegram/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch100@2x.png diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch172@2x.png b/Telegram/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch172@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/AppIcon.appiconset/Watch172@2x.png rename to Telegram/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch172@2x.png diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch196@2x.png b/Telegram/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch196@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/AppIcon.appiconset/Watch196@2x.png rename to Telegram/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch196@2x.png diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch216@2x.png b/Telegram/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch216@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/AppIcon.appiconset/Watch216@2x.png rename to Telegram/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch216@2x.png diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch48@2x.png b/Telegram/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch48@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/AppIcon.appiconset/Watch48@2x.png rename to Telegram/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch48@2x.png diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch55@2x.png b/Telegram/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch55@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/AppIcon.appiconset/Watch55@2x.png rename to Telegram/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch55@2x.png diff --git a/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch88@2x.png b/Telegram/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch88@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/AppIcon.appiconset/Watch88@2x.png rename to Telegram/Watch/App/Assets.xcassets/AppIcon.appiconset/Watch88@2x.png diff --git a/Watch/App/Assets.xcassets/BotCommandIcon.imageset/BotCommandIcon@2x.png b/Telegram/Watch/App/Assets.xcassets/BotCommandIcon.imageset/BotCommandIcon@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BotCommandIcon.imageset/BotCommandIcon@2x.png rename to Telegram/Watch/App/Assets.xcassets/BotCommandIcon.imageset/BotCommandIcon@2x.png diff --git a/Watch/App/Assets.xcassets/BotCommandIcon.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BotCommandIcon.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BotCommandIcon.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BotCommandIcon.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BotKeyboardIcon.imageset/BotKeyboardIcon@2x.png b/Telegram/Watch/App/Assets.xcassets/BotKeyboardIcon.imageset/BotKeyboardIcon@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BotKeyboardIcon.imageset/BotKeyboardIcon@2x.png rename to Telegram/Watch/App/Assets.xcassets/BotKeyboardIcon.imageset/BotKeyboardIcon@2x.png diff --git a/Watch/App/Assets.xcassets/BotKeyboardIcon.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BotKeyboardIcon.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BotKeyboardIcon.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BotKeyboardIcon.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleNotification.imageset/BubbleNotification@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleNotification.imageset/BubbleNotification@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleNotification.imageset/BubbleNotification@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleNotification.imageset/BubbleNotification@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleNotification.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleNotification.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleNotification.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleNotification.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner0.imageset/BubbleSpinner0@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner0.imageset/BubbleSpinner0@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner0.imageset/BubbleSpinner0@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner0.imageset/BubbleSpinner0@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner0.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner0.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner0.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner0.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner1.imageset/BubbleSpinner1@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner1.imageset/BubbleSpinner1@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner1.imageset/BubbleSpinner1@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner1.imageset/BubbleSpinner1@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner1.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner1.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner1.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner1.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner10.imageset/BubbleSpinner10@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner10.imageset/BubbleSpinner10@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner10.imageset/BubbleSpinner10@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner10.imageset/BubbleSpinner10@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner10.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner10.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner10.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner10.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner11.imageset/BubbleSpinner11@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner11.imageset/BubbleSpinner11@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner11.imageset/BubbleSpinner11@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner11.imageset/BubbleSpinner11@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner11.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner11.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner11.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner11.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner12.imageset/BubbleSpinner12@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner12.imageset/BubbleSpinner12@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner12.imageset/BubbleSpinner12@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner12.imageset/BubbleSpinner12@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner12.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner12.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner12.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner12.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner13.imageset/BubbleSpinner13@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner13.imageset/BubbleSpinner13@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner13.imageset/BubbleSpinner13@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner13.imageset/BubbleSpinner13@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner13.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner13.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner13.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner13.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner14.imageset/BubbleSpinner14@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner14.imageset/BubbleSpinner14@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner14.imageset/BubbleSpinner14@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner14.imageset/BubbleSpinner14@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner14.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner14.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner14.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner14.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner15.imageset/BubbleSpinner15@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner15.imageset/BubbleSpinner15@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner15.imageset/BubbleSpinner15@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner15.imageset/BubbleSpinner15@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner15.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner15.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner15.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner15.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner16.imageset/BubbleSpinner16@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner16.imageset/BubbleSpinner16@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner16.imageset/BubbleSpinner16@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner16.imageset/BubbleSpinner16@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner16.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner16.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner16.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner16.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner17.imageset/BubbleSpinner17@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner17.imageset/BubbleSpinner17@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner17.imageset/BubbleSpinner17@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner17.imageset/BubbleSpinner17@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner17.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner17.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner17.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner17.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner18.imageset/BubbleSpinner18@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner18.imageset/BubbleSpinner18@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner18.imageset/BubbleSpinner18@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner18.imageset/BubbleSpinner18@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner18.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner18.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner18.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner18.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner19.imageset/BubbleSpinner19@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner19.imageset/BubbleSpinner19@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner19.imageset/BubbleSpinner19@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner19.imageset/BubbleSpinner19@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner19.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner19.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner19.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner19.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner2.imageset/BubbleSpinner2@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner2.imageset/BubbleSpinner2@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner2.imageset/BubbleSpinner2@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner2.imageset/BubbleSpinner2@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner2.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner2.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner2.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner2.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner20.imageset/BubbleSpinner20@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner20.imageset/BubbleSpinner20@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner20.imageset/BubbleSpinner20@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner20.imageset/BubbleSpinner20@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner20.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner20.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner20.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner20.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner21.imageset/BubbleSpinner21@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner21.imageset/BubbleSpinner21@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner21.imageset/BubbleSpinner21@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner21.imageset/BubbleSpinner21@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner21.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner21.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner21.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner21.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner22.imageset/BubbleSpinner22@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner22.imageset/BubbleSpinner22@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner22.imageset/BubbleSpinner22@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner22.imageset/BubbleSpinner22@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner22.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner22.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner22.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner22.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner23.imageset/BubbleSpinner23@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner23.imageset/BubbleSpinner23@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner23.imageset/BubbleSpinner23@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner23.imageset/BubbleSpinner23@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner23.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner23.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner23.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner23.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner24.imageset/BubbleSpinner24@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner24.imageset/BubbleSpinner24@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner24.imageset/BubbleSpinner24@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner24.imageset/BubbleSpinner24@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner24.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner24.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner24.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner24.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner25.imageset/BubbleSpinner25@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner25.imageset/BubbleSpinner25@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner25.imageset/BubbleSpinner25@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner25.imageset/BubbleSpinner25@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner25.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner25.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner25.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner25.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner26.imageset/BubbleSpinner26@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner26.imageset/BubbleSpinner26@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner26.imageset/BubbleSpinner26@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner26.imageset/BubbleSpinner26@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner26.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner26.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner26.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner26.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner27.imageset/BubbleSpinner27@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner27.imageset/BubbleSpinner27@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner27.imageset/BubbleSpinner27@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner27.imageset/BubbleSpinner27@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner27.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner27.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner27.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner27.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner28.imageset/BubbleSpinner28@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner28.imageset/BubbleSpinner28@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner28.imageset/BubbleSpinner28@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner28.imageset/BubbleSpinner28@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner28.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner28.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner28.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner28.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner29.imageset/BubbleSpinner29@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner29.imageset/BubbleSpinner29@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner29.imageset/BubbleSpinner29@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner29.imageset/BubbleSpinner29@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner29.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner29.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner29.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner29.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner3.imageset/BubbleSpinner3@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner3.imageset/BubbleSpinner3@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner3.imageset/BubbleSpinner3@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner3.imageset/BubbleSpinner3@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner3.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner3.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner3.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner3.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner30.imageset/BubbleSpinner30@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner30.imageset/BubbleSpinner30@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner30.imageset/BubbleSpinner30@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner30.imageset/BubbleSpinner30@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner30.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner30.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner30.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner30.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner31.imageset/BubbleSpinner31@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner31.imageset/BubbleSpinner31@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner31.imageset/BubbleSpinner31@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner31.imageset/BubbleSpinner31@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner31.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner31.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner31.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner31.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner32.imageset/BubbleSpinner32@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner32.imageset/BubbleSpinner32@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner32.imageset/BubbleSpinner32@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner32.imageset/BubbleSpinner32@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner32.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner32.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner32.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner32.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner33.imageset/BubbleSpinner33@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner33.imageset/BubbleSpinner33@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner33.imageset/BubbleSpinner33@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner33.imageset/BubbleSpinner33@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner33.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner33.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner33.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner33.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner34.imageset/BubbleSpinner34@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner34.imageset/BubbleSpinner34@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner34.imageset/BubbleSpinner34@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner34.imageset/BubbleSpinner34@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner34.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner34.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner34.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner34.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner35.imageset/BubbleSpinner35@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner35.imageset/BubbleSpinner35@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner35.imageset/BubbleSpinner35@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner35.imageset/BubbleSpinner35@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner35.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner35.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner35.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner35.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner36.imageset/BubbleSpinner36@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner36.imageset/BubbleSpinner36@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner36.imageset/BubbleSpinner36@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner36.imageset/BubbleSpinner36@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner36.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner36.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner36.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner36.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner37.imageset/BubbleSpinner37@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner37.imageset/BubbleSpinner37@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner37.imageset/BubbleSpinner37@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner37.imageset/BubbleSpinner37@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner37.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner37.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner37.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner37.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner38.imageset/BubbleSpinner38@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner38.imageset/BubbleSpinner38@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner38.imageset/BubbleSpinner38@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner38.imageset/BubbleSpinner38@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner38.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner38.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner38.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner38.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner4.imageset/BubbleSpinner4@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner4.imageset/BubbleSpinner4@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner4.imageset/BubbleSpinner4@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner4.imageset/BubbleSpinner4@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner4.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner4.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner4.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner4.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner5.imageset/BubbleSpinner5@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner5.imageset/BubbleSpinner5@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner5.imageset/BubbleSpinner5@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner5.imageset/BubbleSpinner5@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner5.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner5.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner5.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner5.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner6.imageset/BubbleSpinner6@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner6.imageset/BubbleSpinner6@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner6.imageset/BubbleSpinner6@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner6.imageset/BubbleSpinner6@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner6.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner6.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner6.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner6.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner7.imageset/BubbleSpinner7@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner7.imageset/BubbleSpinner7@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner7.imageset/BubbleSpinner7@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner7.imageset/BubbleSpinner7@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner7.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner7.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner7.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner7.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner8.imageset/BubbleSpinner8@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner8.imageset/BubbleSpinner8@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner8.imageset/BubbleSpinner8@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner8.imageset/BubbleSpinner8@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner8.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner8.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner8.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner8.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner9.imageset/BubbleSpinner9@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner9.imageset/BubbleSpinner9@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner9.imageset/BubbleSpinner9@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner9.imageset/BubbleSpinner9@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner9.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner9.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner9.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/BubbleSpinner9.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinner/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinner/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming0.imageset/BubbleSpinnerIncoming0@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming0.imageset/BubbleSpinnerIncoming0@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming0.imageset/BubbleSpinnerIncoming0@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming0.imageset/BubbleSpinnerIncoming0@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming0.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming0.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming0.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming0.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming1.imageset/BubbleSpinnerIncoming1@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming1.imageset/BubbleSpinnerIncoming1@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming1.imageset/BubbleSpinnerIncoming1@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming1.imageset/BubbleSpinnerIncoming1@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming1.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming1.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming1.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming1.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming10.imageset/BubbleSpinnerIncoming10@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming10.imageset/BubbleSpinnerIncoming10@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming10.imageset/BubbleSpinnerIncoming10@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming10.imageset/BubbleSpinnerIncoming10@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming10.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming10.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming10.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming10.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming11.imageset/BubbleSpinnerIncoming11@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming11.imageset/BubbleSpinnerIncoming11@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming11.imageset/BubbleSpinnerIncoming11@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming11.imageset/BubbleSpinnerIncoming11@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming11.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming11.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming11.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming11.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming12.imageset/BubbleSpinnerIncoming12@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming12.imageset/BubbleSpinnerIncoming12@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming12.imageset/BubbleSpinnerIncoming12@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming12.imageset/BubbleSpinnerIncoming12@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming12.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming12.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming12.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming12.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming13.imageset/BubbleSpinnerIncoming13@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming13.imageset/BubbleSpinnerIncoming13@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming13.imageset/BubbleSpinnerIncoming13@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming13.imageset/BubbleSpinnerIncoming13@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming13.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming13.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming13.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming13.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming14.imageset/BubbleSpinnerIncoming14@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming14.imageset/BubbleSpinnerIncoming14@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming14.imageset/BubbleSpinnerIncoming14@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming14.imageset/BubbleSpinnerIncoming14@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming14.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming14.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming14.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming14.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming15.imageset/BubbleSpinnerIncoming15@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming15.imageset/BubbleSpinnerIncoming15@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming15.imageset/BubbleSpinnerIncoming15@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming15.imageset/BubbleSpinnerIncoming15@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming15.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming15.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming15.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming15.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming16.imageset/BubbleSpinnerIncoming16@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming16.imageset/BubbleSpinnerIncoming16@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming16.imageset/BubbleSpinnerIncoming16@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming16.imageset/BubbleSpinnerIncoming16@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming16.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming16.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming16.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming16.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming17.imageset/BubbleSpinnerIncoming17@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming17.imageset/BubbleSpinnerIncoming17@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming17.imageset/BubbleSpinnerIncoming17@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming17.imageset/BubbleSpinnerIncoming17@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming17.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming17.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming17.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming17.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming18.imageset/BubbleSpinnerIncoming18@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming18.imageset/BubbleSpinnerIncoming18@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming18.imageset/BubbleSpinnerIncoming18@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming18.imageset/BubbleSpinnerIncoming18@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming18.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming18.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming18.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming18.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming19.imageset/BubbleSpinnerIncoming19@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming19.imageset/BubbleSpinnerIncoming19@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming19.imageset/BubbleSpinnerIncoming19@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming19.imageset/BubbleSpinnerIncoming19@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming19.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming19.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming19.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming19.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming2.imageset/BubbleSpinnerIncoming2@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming2.imageset/BubbleSpinnerIncoming2@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming2.imageset/BubbleSpinnerIncoming2@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming2.imageset/BubbleSpinnerIncoming2@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming2.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming2.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming2.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming2.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming20.imageset/BubbleSpinnerIncoming20@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming20.imageset/BubbleSpinnerIncoming20@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming20.imageset/BubbleSpinnerIncoming20@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming20.imageset/BubbleSpinnerIncoming20@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming20.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming20.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming20.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming20.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming21.imageset/BubbleSpinnerIncoming21@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming21.imageset/BubbleSpinnerIncoming21@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming21.imageset/BubbleSpinnerIncoming21@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming21.imageset/BubbleSpinnerIncoming21@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming21.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming21.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming21.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming21.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming22.imageset/BubbleSpinnerIncoming22@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming22.imageset/BubbleSpinnerIncoming22@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming22.imageset/BubbleSpinnerIncoming22@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming22.imageset/BubbleSpinnerIncoming22@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming22.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming22.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming22.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming22.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming23.imageset/BubbleSpinnerIncoming23@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming23.imageset/BubbleSpinnerIncoming23@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming23.imageset/BubbleSpinnerIncoming23@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming23.imageset/BubbleSpinnerIncoming23@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming23.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming23.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming23.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming23.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming24.imageset/BubbleSpinnerIncoming24@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming24.imageset/BubbleSpinnerIncoming24@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming24.imageset/BubbleSpinnerIncoming24@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming24.imageset/BubbleSpinnerIncoming24@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming24.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming24.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming24.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming24.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming25.imageset/BubbleSpinnerIncoming25@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming25.imageset/BubbleSpinnerIncoming25@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming25.imageset/BubbleSpinnerIncoming25@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming25.imageset/BubbleSpinnerIncoming25@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming25.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming25.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming25.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming25.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming26.imageset/BubbleSpinnerIncoming26@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming26.imageset/BubbleSpinnerIncoming26@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming26.imageset/BubbleSpinnerIncoming26@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming26.imageset/BubbleSpinnerIncoming26@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming26.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming26.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming26.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming26.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming27.imageset/BubbleSpinnerIncoming27@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming27.imageset/BubbleSpinnerIncoming27@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming27.imageset/BubbleSpinnerIncoming27@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming27.imageset/BubbleSpinnerIncoming27@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming27.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming27.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming27.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming27.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming28.imageset/BubbleSpinnerIncoming28@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming28.imageset/BubbleSpinnerIncoming28@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming28.imageset/BubbleSpinnerIncoming28@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming28.imageset/BubbleSpinnerIncoming28@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming28.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming28.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming28.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming28.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming29.imageset/BubbleSpinnerIncoming29@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming29.imageset/BubbleSpinnerIncoming29@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming29.imageset/BubbleSpinnerIncoming29@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming29.imageset/BubbleSpinnerIncoming29@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming29.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming29.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming29.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming29.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming3.imageset/BubbleSpinnerIncoming3@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming3.imageset/BubbleSpinnerIncoming3@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming3.imageset/BubbleSpinnerIncoming3@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming3.imageset/BubbleSpinnerIncoming3@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming3.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming3.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming3.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming3.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming30.imageset/BubbleSpinnerIncoming30@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming30.imageset/BubbleSpinnerIncoming30@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming30.imageset/BubbleSpinnerIncoming30@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming30.imageset/BubbleSpinnerIncoming30@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming30.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming30.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming30.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming30.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming31.imageset/BubbleSpinnerIncoming31@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming31.imageset/BubbleSpinnerIncoming31@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming31.imageset/BubbleSpinnerIncoming31@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming31.imageset/BubbleSpinnerIncoming31@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming31.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming31.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming31.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming31.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming32.imageset/BubbleSpinnerIncoming32@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming32.imageset/BubbleSpinnerIncoming32@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming32.imageset/BubbleSpinnerIncoming32@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming32.imageset/BubbleSpinnerIncoming32@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming32.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming32.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming32.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming32.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming33.imageset/BubbleSpinnerIncoming33@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming33.imageset/BubbleSpinnerIncoming33@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming33.imageset/BubbleSpinnerIncoming33@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming33.imageset/BubbleSpinnerIncoming33@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming33.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming33.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming33.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming33.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming34.imageset/BubbleSpinnerIncoming34@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming34.imageset/BubbleSpinnerIncoming34@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming34.imageset/BubbleSpinnerIncoming34@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming34.imageset/BubbleSpinnerIncoming34@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming34.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming34.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming34.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming34.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming35.imageset/BubbleSpinnerIncoming35@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming35.imageset/BubbleSpinnerIncoming35@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming35.imageset/BubbleSpinnerIncoming35@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming35.imageset/BubbleSpinnerIncoming35@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming35.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming35.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming35.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming35.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming36.imageset/BubbleSpinnerIncoming36@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming36.imageset/BubbleSpinnerIncoming36@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming36.imageset/BubbleSpinnerIncoming36@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming36.imageset/BubbleSpinnerIncoming36@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming36.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming36.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming36.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming36.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming37.imageset/BubbleSpinnerIncoming37@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming37.imageset/BubbleSpinnerIncoming37@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming37.imageset/BubbleSpinnerIncoming37@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming37.imageset/BubbleSpinnerIncoming37@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming37.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming37.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming37.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming37.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming38.imageset/BubbleSpinnerIncoming38@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming38.imageset/BubbleSpinnerIncoming38@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming38.imageset/BubbleSpinnerIncoming38@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming38.imageset/BubbleSpinnerIncoming38@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming38.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming38.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming38.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming38.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming4.imageset/BubbleSpinnerIncoming4@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming4.imageset/BubbleSpinnerIncoming4@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming4.imageset/BubbleSpinnerIncoming4@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming4.imageset/BubbleSpinnerIncoming4@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming4.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming4.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming4.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming4.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming5.imageset/BubbleSpinnerIncoming5@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming5.imageset/BubbleSpinnerIncoming5@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming5.imageset/BubbleSpinnerIncoming5@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming5.imageset/BubbleSpinnerIncoming5@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming5.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming5.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming5.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming5.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming6.imageset/BubbleSpinnerIncoming6@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming6.imageset/BubbleSpinnerIncoming6@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming6.imageset/BubbleSpinnerIncoming6@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming6.imageset/BubbleSpinnerIncoming6@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming6.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming6.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming6.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming6.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming7.imageset/BubbleSpinnerIncoming7@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming7.imageset/BubbleSpinnerIncoming7@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming7.imageset/BubbleSpinnerIncoming7@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming7.imageset/BubbleSpinnerIncoming7@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming7.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming7.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming7.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming7.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming8.imageset/BubbleSpinnerIncoming8@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming8.imageset/BubbleSpinnerIncoming8@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming8.imageset/BubbleSpinnerIncoming8@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming8.imageset/BubbleSpinnerIncoming8@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming8.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming8.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming8.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming8.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming9.imageset/BubbleSpinnerIncoming9@2x.png b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming9.imageset/BubbleSpinnerIncoming9@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming9.imageset/BubbleSpinnerIncoming9@2x.png rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming9.imageset/BubbleSpinnerIncoming9@2x.png diff --git a/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming9.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming9.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming9.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/BubbleSpinnerIncoming9.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Contents.json b/Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Contents.json rename to Telegram/Watch/App/Assets.xcassets/BubbleSpinnerIncoming/Contents.json diff --git a/Watch/App/Assets.xcassets/ChatBubbleChannel.imageset/BubbleChannel@2x.png b/Telegram/Watch/App/Assets.xcassets/ChatBubbleChannel.imageset/BubbleChannel@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/ChatBubbleChannel.imageset/BubbleChannel@2x.png rename to Telegram/Watch/App/Assets.xcassets/ChatBubbleChannel.imageset/BubbleChannel@2x.png diff --git a/Watch/App/Assets.xcassets/ChatBubbleChannel.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/ChatBubbleChannel.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/ChatBubbleChannel.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/ChatBubbleChannel.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/ChatBubbleIncoming.imageset/BubbleIncoming@2x.png b/Telegram/Watch/App/Assets.xcassets/ChatBubbleIncoming.imageset/BubbleIncoming@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/ChatBubbleIncoming.imageset/BubbleIncoming@2x.png rename to Telegram/Watch/App/Assets.xcassets/ChatBubbleIncoming.imageset/BubbleIncoming@2x.png diff --git a/Watch/App/Assets.xcassets/ChatBubbleIncoming.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/ChatBubbleIncoming.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/ChatBubbleIncoming.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/ChatBubbleIncoming.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/ChatBubbleOutgoing.imageset/BubbleOutgoing@2x.png b/Telegram/Watch/App/Assets.xcassets/ChatBubbleOutgoing.imageset/BubbleOutgoing@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/ChatBubbleOutgoing.imageset/BubbleOutgoing@2x.png rename to Telegram/Watch/App/Assets.xcassets/ChatBubbleOutgoing.imageset/BubbleOutgoing@2x.png diff --git a/Watch/App/Assets.xcassets/ChatBubbleOutgoing.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/ChatBubbleOutgoing.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/ChatBubbleOutgoing.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/ChatBubbleOutgoing.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Complication.complicationset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Complication.complicationset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Complication.complicationset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Complication.complicationset/Contents.json diff --git a/Watch/App/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Complication.complicationset/Graphic Large Rectangular.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Complication.complicationset/Graphic Large Rectangular.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Complication.complicationset/Graphic Large Rectangular.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Complication.complicationset/Graphic Large Rectangular.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Compose.imageset/Compose@2x.png b/Telegram/Watch/App/Assets.xcassets/Compose.imageset/Compose@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Compose.imageset/Compose@2x.png rename to Telegram/Watch/App/Assets.xcassets/Compose.imageset/Compose@2x.png diff --git a/Watch/App/Assets.xcassets/Compose.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Compose.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Compose.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Compose.imageset/Contents.json diff --git a/Telegram/Watch/App/Assets.xcassets/Contents.json b/Telegram/Watch/App/Assets.xcassets/Contents.json new file mode 100644 index 0000000000..da4a164c91 --- /dev/null +++ b/Telegram/Watch/App/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Watch/App/Assets.xcassets/File.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/File.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/File.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/File.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/File.imageset/File@2x.png b/Telegram/Watch/App/Assets.xcassets/File.imageset/File@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/File.imageset/File@2x.png rename to Telegram/Watch/App/Assets.xcassets/File.imageset/File@2x.png diff --git a/Watch/App/Assets.xcassets/LocationIcon.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/LocationIcon.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/LocationIcon.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/LocationIcon.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/LocationIcon.imageset/LocationIcon@2x.png b/Telegram/Watch/App/Assets.xcassets/LocationIcon.imageset/LocationIcon@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/LocationIcon.imageset/LocationIcon@2x.png rename to Telegram/Watch/App/Assets.xcassets/LocationIcon.imageset/LocationIcon@2x.png diff --git a/Watch/App/Assets.xcassets/LoginIcon.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/LoginIcon.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/LoginIcon.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/LoginIcon.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/LoginIcon.imageset/LoginIcon@2x.png b/Telegram/Watch/App/Assets.xcassets/LoginIcon.imageset/LoginIcon@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/LoginIcon.imageset/LoginIcon@2x.png rename to Telegram/Watch/App/Assets.xcassets/LoginIcon.imageset/LoginIcon@2x.png diff --git a/Watch/App/Assets.xcassets/MediaAudio.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/MediaAudio.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/MediaAudio.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/MediaAudio.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/MediaAudio.imageset/MediaAudio@2x.png b/Telegram/Watch/App/Assets.xcassets/MediaAudio.imageset/MediaAudio@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/MediaAudio.imageset/MediaAudio@2x.png rename to Telegram/Watch/App/Assets.xcassets/MediaAudio.imageset/MediaAudio@2x.png diff --git a/Watch/App/Assets.xcassets/MediaAudioPlay.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/MediaAudioPlay.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/MediaAudioPlay.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/MediaAudioPlay.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/MediaAudioPlay.imageset/MediaAudioPlay@2x.png b/Telegram/Watch/App/Assets.xcassets/MediaAudioPlay.imageset/MediaAudioPlay@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/MediaAudioPlay.imageset/MediaAudioPlay@2x.png rename to Telegram/Watch/App/Assets.xcassets/MediaAudioPlay.imageset/MediaAudioPlay@2x.png diff --git a/Watch/App/Assets.xcassets/MediaDocument.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/MediaDocument.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/MediaDocument.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/MediaDocument.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/MediaDocument.imageset/MediaDocument@2x.png b/Telegram/Watch/App/Assets.xcassets/MediaDocument.imageset/MediaDocument@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/MediaDocument.imageset/MediaDocument@2x.png rename to Telegram/Watch/App/Assets.xcassets/MediaDocument.imageset/MediaDocument@2x.png diff --git a/Watch/App/Assets.xcassets/MediaLocation.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/MediaLocation.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/MediaLocation.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/MediaLocation.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/MediaLocation.imageset/MediaLocation@2x.png b/Telegram/Watch/App/Assets.xcassets/MediaLocation.imageset/MediaLocation@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/MediaLocation.imageset/MediaLocation@2x.png rename to Telegram/Watch/App/Assets.xcassets/MediaLocation.imageset/MediaLocation@2x.png diff --git a/Watch/App/Assets.xcassets/MediaPhoto.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/MediaPhoto.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/MediaPhoto.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/MediaPhoto.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/MediaPhoto.imageset/MediaPhoto@2x.png b/Telegram/Watch/App/Assets.xcassets/MediaPhoto.imageset/MediaPhoto@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/MediaPhoto.imageset/MediaPhoto@2x.png rename to Telegram/Watch/App/Assets.xcassets/MediaPhoto.imageset/MediaPhoto@2x.png diff --git a/Watch/App/Assets.xcassets/MediaVideo.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/MediaVideo.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/MediaVideo.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/MediaVideo.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/MediaVideo.imageset/MediaVideo@2x.png b/Telegram/Watch/App/Assets.xcassets/MediaVideo.imageset/MediaVideo@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/MediaVideo.imageset/MediaVideo@2x.png rename to Telegram/Watch/App/Assets.xcassets/MediaVideo.imageset/MediaVideo@2x.png diff --git a/Watch/App/Assets.xcassets/MessageStatusDot.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/MessageStatusDot.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/MessageStatusDot.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/MessageStatusDot.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/MessageStatusDot.imageset/MessageStatusDot@2x.png b/Telegram/Watch/App/Assets.xcassets/MessageStatusDot.imageset/MessageStatusDot@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/MessageStatusDot.imageset/MessageStatusDot@2x.png rename to Telegram/Watch/App/Assets.xcassets/MessageStatusDot.imageset/MessageStatusDot@2x.png diff --git a/Watch/App/Assets.xcassets/MicAccessIcon.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/MicAccessIcon.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/MicAccessIcon.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/MicAccessIcon.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/MicAccessIcon.imageset/MicIcon@2x.png b/Telegram/Watch/App/Assets.xcassets/MicAccessIcon.imageset/MicIcon@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/MicAccessIcon.imageset/MicIcon@2x.png rename to Telegram/Watch/App/Assets.xcassets/MicAccessIcon.imageset/MicIcon@2x.png diff --git a/Watch/App/Assets.xcassets/MicIcon.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/MicIcon.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/MicIcon.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/MicIcon.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/MicIcon.imageset/Mic@2x.png b/Telegram/Watch/App/Assets.xcassets/MicIcon.imageset/Mic@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/MicIcon.imageset/Mic@2x.png rename to Telegram/Watch/App/Assets.xcassets/MicIcon.imageset/Mic@2x.png diff --git a/Watch/App/Assets.xcassets/PasscodeIcon.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/PasscodeIcon.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/PasscodeIcon.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/PasscodeIcon.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/PasscodeIcon.imageset/Passcode@2x.png b/Telegram/Watch/App/Assets.xcassets/PasscodeIcon.imageset/Passcode@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/PasscodeIcon.imageset/Passcode@2x.png rename to Telegram/Watch/App/Assets.xcassets/PasscodeIcon.imageset/Passcode@2x.png diff --git a/Watch/App/Assets.xcassets/PickLocation.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/PickLocation.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/PickLocation.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/PickLocation.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/PickLocation.imageset/PickLocation@2x.png b/Telegram/Watch/App/Assets.xcassets/PickLocation.imageset/PickLocation@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/PickLocation.imageset/PickLocation@2x.png rename to Telegram/Watch/App/Assets.xcassets/PickLocation.imageset/PickLocation@2x.png diff --git a/Watch/App/Assets.xcassets/RemotePhone.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/RemotePhone.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/RemotePhone.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/RemotePhone.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/RemotePhone.imageset/Phone@2x.png b/Telegram/Watch/App/Assets.xcassets/RemotePhone.imageset/Phone@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/RemotePhone.imageset/Phone@2x.png rename to Telegram/Watch/App/Assets.xcassets/RemotePhone.imageset/Phone@2x.png diff --git a/Watch/App/Assets.xcassets/RemotePlayVideo.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/RemotePlayVideo.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/RemotePlayVideo.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/RemotePlayVideo.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/RemotePlayVideo.imageset/RemotePlayVideo@2x.png b/Telegram/Watch/App/Assets.xcassets/RemotePlayVideo.imageset/RemotePlayVideo@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/RemotePlayVideo.imageset/RemotePlayVideo@2x.png rename to Telegram/Watch/App/Assets.xcassets/RemotePlayVideo.imageset/RemotePlayVideo@2x.png diff --git a/Watch/App/Assets.xcassets/SavedMessages.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/SavedMessages.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/SavedMessages.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/SavedMessages.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/SavedMessages.imageset/SavedMessages@2x.png b/Telegram/Watch/App/Assets.xcassets/SavedMessages.imageset/SavedMessages@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/SavedMessages.imageset/SavedMessages@2x.png rename to Telegram/Watch/App/Assets.xcassets/SavedMessages.imageset/SavedMessages@2x.png diff --git a/Watch/App/Assets.xcassets/SavedMessagesAvatar.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/SavedMessagesAvatar.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/SavedMessagesAvatar.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/SavedMessagesAvatar.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/SavedMessagesAvatar.imageset/SavedMessagesAvatar@2x.png b/Telegram/Watch/App/Assets.xcassets/SavedMessagesAvatar.imageset/SavedMessagesAvatar@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/SavedMessagesAvatar.imageset/SavedMessagesAvatar@2x.png rename to Telegram/Watch/App/Assets.xcassets/SavedMessagesAvatar.imageset/SavedMessagesAvatar@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner0.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner0.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner0.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner0.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner0.imageset/Spinner0@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner0.imageset/Spinner0@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner0.imageset/Spinner0@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner0.imageset/Spinner0@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner1.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner1.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner1.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner1.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner1.imageset/Spinner1@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner1.imageset/Spinner1@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner1.imageset/Spinner1@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner1.imageset/Spinner1@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner10.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner10.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner10.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner10.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner10.imageset/Spinner10@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner10.imageset/Spinner10@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner10.imageset/Spinner10@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner10.imageset/Spinner10@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner11.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner11.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner11.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner11.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner11.imageset/Spinner11@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner11.imageset/Spinner11@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner11.imageset/Spinner11@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner11.imageset/Spinner11@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner12.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner12.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner12.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner12.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner12.imageset/Spinner12@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner12.imageset/Spinner12@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner12.imageset/Spinner12@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner12.imageset/Spinner12@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner13.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner13.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner13.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner13.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner13.imageset/Spinner13@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner13.imageset/Spinner13@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner13.imageset/Spinner13@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner13.imageset/Spinner13@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner14.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner14.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner14.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner14.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner14.imageset/Spinner14@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner14.imageset/Spinner14@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner14.imageset/Spinner14@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner14.imageset/Spinner14@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner15.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner15.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner15.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner15.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner15.imageset/Spinner15@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner15.imageset/Spinner15@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner15.imageset/Spinner15@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner15.imageset/Spinner15@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner16.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner16.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner16.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner16.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner16.imageset/Spinner16@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner16.imageset/Spinner16@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner16.imageset/Spinner16@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner16.imageset/Spinner16@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner17.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner17.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner17.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner17.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner17.imageset/Spinner17@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner17.imageset/Spinner17@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner17.imageset/Spinner17@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner17.imageset/Spinner17@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner18.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner18.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner18.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner18.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner18.imageset/Spinner18@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner18.imageset/Spinner18@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner18.imageset/Spinner18@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner18.imageset/Spinner18@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner19.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner19.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner19.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner19.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner19.imageset/Spinner19@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner19.imageset/Spinner19@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner19.imageset/Spinner19@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner19.imageset/Spinner19@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner2.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner2.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner2.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner2.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner2.imageset/Spinner2@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner2.imageset/Spinner2@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner2.imageset/Spinner2@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner2.imageset/Spinner2@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner20.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner20.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner20.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner20.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner20.imageset/Spinner20@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner20.imageset/Spinner20@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner20.imageset/Spinner20@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner20.imageset/Spinner20@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner21.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner21.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner21.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner21.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner21.imageset/Spinner21@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner21.imageset/Spinner21@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner21.imageset/Spinner21@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner21.imageset/Spinner21@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner22.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner22.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner22.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner22.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner22.imageset/Spinner22@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner22.imageset/Spinner22@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner22.imageset/Spinner22@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner22.imageset/Spinner22@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner23.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner23.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner23.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner23.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner23.imageset/Spinner23@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner23.imageset/Spinner23@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner23.imageset/Spinner23@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner23.imageset/Spinner23@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner24.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner24.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner24.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner24.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner24.imageset/Spinner24@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner24.imageset/Spinner24@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner24.imageset/Spinner24@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner24.imageset/Spinner24@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner25.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner25.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner25.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner25.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner25.imageset/Spinner25@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner25.imageset/Spinner25@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner25.imageset/Spinner25@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner25.imageset/Spinner25@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner26.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner26.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner26.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner26.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner26.imageset/Spinner26@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner26.imageset/Spinner26@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner26.imageset/Spinner26@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner26.imageset/Spinner26@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner27.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner27.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner27.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner27.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner27.imageset/Spinner27@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner27.imageset/Spinner27@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner27.imageset/Spinner27@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner27.imageset/Spinner27@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner28.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner28.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner28.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner28.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner28.imageset/Spinner28@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner28.imageset/Spinner28@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner28.imageset/Spinner28@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner28.imageset/Spinner28@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner29.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner29.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner29.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner29.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner29.imageset/Spinner29@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner29.imageset/Spinner29@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner29.imageset/Spinner29@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner29.imageset/Spinner29@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner3.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner3.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner3.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner3.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner3.imageset/Spinner3@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner3.imageset/Spinner3@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner3.imageset/Spinner3@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner3.imageset/Spinner3@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner30.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner30.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner30.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner30.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner30.imageset/Spinner30@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner30.imageset/Spinner30@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner30.imageset/Spinner30@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner30.imageset/Spinner30@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner31.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner31.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner31.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner31.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner31.imageset/Spinner31@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner31.imageset/Spinner31@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner31.imageset/Spinner31@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner31.imageset/Spinner31@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner32.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner32.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner32.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner32.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner32.imageset/Spinner32@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner32.imageset/Spinner32@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner32.imageset/Spinner32@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner32.imageset/Spinner32@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner33.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner33.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner33.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner33.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner33.imageset/Spinner33@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner33.imageset/Spinner33@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner33.imageset/Spinner33@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner33.imageset/Spinner33@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner34.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner34.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner34.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner34.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner34.imageset/Spinner34@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner34.imageset/Spinner34@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner34.imageset/Spinner34@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner34.imageset/Spinner34@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner35.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner35.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner35.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner35.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner35.imageset/Spinner35@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner35.imageset/Spinner35@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner35.imageset/Spinner35@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner35.imageset/Spinner35@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner36.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner36.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner36.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner36.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner36.imageset/Spinner36@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner36.imageset/Spinner36@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner36.imageset/Spinner36@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner36.imageset/Spinner36@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner37.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner37.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner37.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner37.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner37.imageset/Spinner37@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner37.imageset/Spinner37@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner37.imageset/Spinner37@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner37.imageset/Spinner37@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner38.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner38.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner38.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner38.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner38.imageset/Spinner38@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner38.imageset/Spinner38@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner38.imageset/Spinner38@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner38.imageset/Spinner38@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner4.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner4.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner4.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner4.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner4.imageset/Spinner4@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner4.imageset/Spinner4@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner4.imageset/Spinner4@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner4.imageset/Spinner4@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner5.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner5.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner5.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner5.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner5.imageset/Spinner5@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner5.imageset/Spinner5@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner5.imageset/Spinner5@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner5.imageset/Spinner5@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner6.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner6.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner6.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner6.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner6.imageset/Spinner6@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner6.imageset/Spinner6@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner6.imageset/Spinner6@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner6.imageset/Spinner6@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner7.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner7.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner7.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner7.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner7.imageset/Spinner7@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner7.imageset/Spinner7@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner7.imageset/Spinner7@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner7.imageset/Spinner7@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner8.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner8.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner8.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner8.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner8.imageset/Spinner8@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner8.imageset/Spinner8@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner8.imageset/Spinner8@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner8.imageset/Spinner8@2x.png diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner9.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner9.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner9.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner9.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/Spinner/Spinner9.imageset/Spinner9@2x.png b/Telegram/Watch/App/Assets.xcassets/Spinner/Spinner9.imageset/Spinner9@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/Spinner/Spinner9.imageset/Spinner9@2x.png rename to Telegram/Watch/App/Assets.xcassets/Spinner/Spinner9.imageset/Spinner9@2x.png diff --git a/Watch/App/Assets.xcassets/StatusDot.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/StatusDot.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/StatusDot.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/StatusDot.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/StatusDot.imageset/StatusDot@2x.png b/Telegram/Watch/App/Assets.xcassets/StatusDot.imageset/StatusDot@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/StatusDot.imageset/StatusDot@2x.png rename to Telegram/Watch/App/Assets.xcassets/StatusDot.imageset/StatusDot@2x.png diff --git a/Watch/App/Assets.xcassets/StickerIcon.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/StickerIcon.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/StickerIcon.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/StickerIcon.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/StickerIcon.imageset/StickerIcon@2x.png b/Telegram/Watch/App/Assets.xcassets/StickerIcon.imageset/StickerIcon@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/StickerIcon.imageset/StickerIcon@2x.png rename to Telegram/Watch/App/Assets.xcassets/StickerIcon.imageset/StickerIcon@2x.png diff --git a/Watch/App/Assets.xcassets/VerifiedProfile.imageset/Contents.json b/Telegram/Watch/App/Assets.xcassets/VerifiedProfile.imageset/Contents.json similarity index 100% rename from Watch/App/Assets.xcassets/VerifiedProfile.imageset/Contents.json rename to Telegram/Watch/App/Assets.xcassets/VerifiedProfile.imageset/Contents.json diff --git a/Watch/App/Assets.xcassets/VerifiedProfile.imageset/VerifiedProfile@2x.png b/Telegram/Watch/App/Assets.xcassets/VerifiedProfile.imageset/VerifiedProfile@2x.png similarity index 100% rename from Watch/App/Assets.xcassets/VerifiedProfile.imageset/VerifiedProfile@2x.png rename to Telegram/Watch/App/Assets.xcassets/VerifiedProfile.imageset/VerifiedProfile@2x.png diff --git a/Watch/App/Base.lproj/Interface.storyboard b/Telegram/Watch/App/Base.lproj/Interface.storyboard similarity index 100% rename from Watch/App/Base.lproj/Interface.storyboard rename to Telegram/Watch/App/Base.lproj/Interface.storyboard diff --git a/Watch/App/Info.plist b/Telegram/Watch/App/Info.plist similarity index 100% rename from Watch/App/Info.plist rename to Telegram/Watch/App/Info.plist diff --git a/Watch/Bridge/TGBridgeAudioSignals.h b/Telegram/Watch/Bridge/TGBridgeAudioSignals.h similarity index 100% rename from Watch/Bridge/TGBridgeAudioSignals.h rename to Telegram/Watch/Bridge/TGBridgeAudioSignals.h diff --git a/Watch/Bridge/TGBridgeAudioSignals.m b/Telegram/Watch/Bridge/TGBridgeAudioSignals.m similarity index 100% rename from Watch/Bridge/TGBridgeAudioSignals.m rename to Telegram/Watch/Bridge/TGBridgeAudioSignals.m diff --git a/Watch/Bridge/TGBridgeBotReplyMarkup.h b/Telegram/Watch/Bridge/TGBridgeBotReplyMarkup.h similarity index 100% rename from Watch/Bridge/TGBridgeBotReplyMarkup.h rename to Telegram/Watch/Bridge/TGBridgeBotReplyMarkup.h diff --git a/Watch/Bridge/TGBridgeBotReplyMarkup.m b/Telegram/Watch/Bridge/TGBridgeBotReplyMarkup.m similarity index 100% rename from Watch/Bridge/TGBridgeBotReplyMarkup.m rename to Telegram/Watch/Bridge/TGBridgeBotReplyMarkup.m diff --git a/Watch/Bridge/TGBridgeBotSignals.h b/Telegram/Watch/Bridge/TGBridgeBotSignals.h similarity index 100% rename from Watch/Bridge/TGBridgeBotSignals.h rename to Telegram/Watch/Bridge/TGBridgeBotSignals.h diff --git a/Watch/Bridge/TGBridgeBotSignals.m b/Telegram/Watch/Bridge/TGBridgeBotSignals.m similarity index 100% rename from Watch/Bridge/TGBridgeBotSignals.m rename to Telegram/Watch/Bridge/TGBridgeBotSignals.m diff --git a/Watch/Bridge/TGBridgeChat+TGTableItem.h b/Telegram/Watch/Bridge/TGBridgeChat+TGTableItem.h similarity index 100% rename from Watch/Bridge/TGBridgeChat+TGTableItem.h rename to Telegram/Watch/Bridge/TGBridgeChat+TGTableItem.h diff --git a/Watch/Bridge/TGBridgeChat+TGTableItem.m b/Telegram/Watch/Bridge/TGBridgeChat+TGTableItem.m similarity index 100% rename from Watch/Bridge/TGBridgeChat+TGTableItem.m rename to Telegram/Watch/Bridge/TGBridgeChat+TGTableItem.m diff --git a/Watch/Bridge/TGBridgeChatListSignals.h b/Telegram/Watch/Bridge/TGBridgeChatListSignals.h similarity index 100% rename from Watch/Bridge/TGBridgeChatListSignals.h rename to Telegram/Watch/Bridge/TGBridgeChatListSignals.h diff --git a/Watch/Bridge/TGBridgeChatListSignals.m b/Telegram/Watch/Bridge/TGBridgeChatListSignals.m similarity index 100% rename from Watch/Bridge/TGBridgeChatListSignals.m rename to Telegram/Watch/Bridge/TGBridgeChatListSignals.m diff --git a/Watch/Bridge/TGBridgeChatMessageListSignals.h b/Telegram/Watch/Bridge/TGBridgeChatMessageListSignals.h similarity index 100% rename from Watch/Bridge/TGBridgeChatMessageListSignals.h rename to Telegram/Watch/Bridge/TGBridgeChatMessageListSignals.h diff --git a/Watch/Bridge/TGBridgeChatMessageListSignals.m b/Telegram/Watch/Bridge/TGBridgeChatMessageListSignals.m similarity index 100% rename from Watch/Bridge/TGBridgeChatMessageListSignals.m rename to Telegram/Watch/Bridge/TGBridgeChatMessageListSignals.m diff --git a/Watch/Bridge/TGBridgeClient.h b/Telegram/Watch/Bridge/TGBridgeClient.h similarity index 100% rename from Watch/Bridge/TGBridgeClient.h rename to Telegram/Watch/Bridge/TGBridgeClient.h diff --git a/Watch/Bridge/TGBridgeClient.m b/Telegram/Watch/Bridge/TGBridgeClient.m similarity index 100% rename from Watch/Bridge/TGBridgeClient.m rename to Telegram/Watch/Bridge/TGBridgeClient.m diff --git a/Watch/Bridge/TGBridgeContactsSignals.h b/Telegram/Watch/Bridge/TGBridgeContactsSignals.h similarity index 100% rename from Watch/Bridge/TGBridgeContactsSignals.h rename to Telegram/Watch/Bridge/TGBridgeContactsSignals.h diff --git a/Watch/Bridge/TGBridgeContactsSignals.m b/Telegram/Watch/Bridge/TGBridgeContactsSignals.m similarity index 100% rename from Watch/Bridge/TGBridgeContactsSignals.m rename to Telegram/Watch/Bridge/TGBridgeContactsSignals.m diff --git a/Watch/Bridge/TGBridgeConversationSignals.h b/Telegram/Watch/Bridge/TGBridgeConversationSignals.h similarity index 100% rename from Watch/Bridge/TGBridgeConversationSignals.h rename to Telegram/Watch/Bridge/TGBridgeConversationSignals.h diff --git a/Watch/Bridge/TGBridgeConversationSignals.m b/Telegram/Watch/Bridge/TGBridgeConversationSignals.m similarity index 100% rename from Watch/Bridge/TGBridgeConversationSignals.m rename to Telegram/Watch/Bridge/TGBridgeConversationSignals.m diff --git a/Watch/Bridge/TGBridgeLocationSignals.h b/Telegram/Watch/Bridge/TGBridgeLocationSignals.h similarity index 100% rename from Watch/Bridge/TGBridgeLocationSignals.h rename to Telegram/Watch/Bridge/TGBridgeLocationSignals.h diff --git a/Watch/Bridge/TGBridgeLocationSignals.m b/Telegram/Watch/Bridge/TGBridgeLocationSignals.m similarity index 100% rename from Watch/Bridge/TGBridgeLocationSignals.m rename to Telegram/Watch/Bridge/TGBridgeLocationSignals.m diff --git a/Watch/Bridge/TGBridgeLocationVenue+TGTableItem.h b/Telegram/Watch/Bridge/TGBridgeLocationVenue+TGTableItem.h similarity index 100% rename from Watch/Bridge/TGBridgeLocationVenue+TGTableItem.h rename to Telegram/Watch/Bridge/TGBridgeLocationVenue+TGTableItem.h diff --git a/Watch/Bridge/TGBridgeLocationVenue+TGTableItem.m b/Telegram/Watch/Bridge/TGBridgeLocationVenue+TGTableItem.m similarity index 100% rename from Watch/Bridge/TGBridgeLocationVenue+TGTableItem.m rename to Telegram/Watch/Bridge/TGBridgeLocationVenue+TGTableItem.m diff --git a/Watch/Bridge/TGBridgeMediaSignals.h b/Telegram/Watch/Bridge/TGBridgeMediaSignals.h similarity index 100% rename from Watch/Bridge/TGBridgeMediaSignals.h rename to Telegram/Watch/Bridge/TGBridgeMediaSignals.h diff --git a/Watch/Bridge/TGBridgeMediaSignals.m b/Telegram/Watch/Bridge/TGBridgeMediaSignals.m similarity index 100% rename from Watch/Bridge/TGBridgeMediaSignals.m rename to Telegram/Watch/Bridge/TGBridgeMediaSignals.m diff --git a/Watch/Bridge/TGBridgeMessage+TGTableItem.h b/Telegram/Watch/Bridge/TGBridgeMessage+TGTableItem.h similarity index 100% rename from Watch/Bridge/TGBridgeMessage+TGTableItem.h rename to Telegram/Watch/Bridge/TGBridgeMessage+TGTableItem.h diff --git a/Watch/Bridge/TGBridgeMessage+TGTableItem.m b/Telegram/Watch/Bridge/TGBridgeMessage+TGTableItem.m similarity index 100% rename from Watch/Bridge/TGBridgeMessage+TGTableItem.m rename to Telegram/Watch/Bridge/TGBridgeMessage+TGTableItem.m diff --git a/Watch/Bridge/TGBridgePeerSettingsSignals.h b/Telegram/Watch/Bridge/TGBridgePeerSettingsSignals.h similarity index 100% rename from Watch/Bridge/TGBridgePeerSettingsSignals.h rename to Telegram/Watch/Bridge/TGBridgePeerSettingsSignals.h diff --git a/Watch/Bridge/TGBridgePeerSettingsSignals.m b/Telegram/Watch/Bridge/TGBridgePeerSettingsSignals.m similarity index 100% rename from Watch/Bridge/TGBridgePeerSettingsSignals.m rename to Telegram/Watch/Bridge/TGBridgePeerSettingsSignals.m diff --git a/Watch/Bridge/TGBridgePresetsSignals.h b/Telegram/Watch/Bridge/TGBridgePresetsSignals.h similarity index 100% rename from Watch/Bridge/TGBridgePresetsSignals.h rename to Telegram/Watch/Bridge/TGBridgePresetsSignals.h diff --git a/Watch/Bridge/TGBridgePresetsSignals.m b/Telegram/Watch/Bridge/TGBridgePresetsSignals.m similarity index 100% rename from Watch/Bridge/TGBridgePresetsSignals.m rename to Telegram/Watch/Bridge/TGBridgePresetsSignals.m diff --git a/Watch/Bridge/TGBridgeRemoteSignals.h b/Telegram/Watch/Bridge/TGBridgeRemoteSignals.h similarity index 100% rename from Watch/Bridge/TGBridgeRemoteSignals.h rename to Telegram/Watch/Bridge/TGBridgeRemoteSignals.h diff --git a/Watch/Bridge/TGBridgeRemoteSignals.m b/Telegram/Watch/Bridge/TGBridgeRemoteSignals.m similarity index 100% rename from Watch/Bridge/TGBridgeRemoteSignals.m rename to Telegram/Watch/Bridge/TGBridgeRemoteSignals.m diff --git a/Watch/Bridge/TGBridgeSendMessageSignals.h b/Telegram/Watch/Bridge/TGBridgeSendMessageSignals.h similarity index 100% rename from Watch/Bridge/TGBridgeSendMessageSignals.h rename to Telegram/Watch/Bridge/TGBridgeSendMessageSignals.h diff --git a/Watch/Bridge/TGBridgeSendMessageSignals.m b/Telegram/Watch/Bridge/TGBridgeSendMessageSignals.m similarity index 100% rename from Watch/Bridge/TGBridgeSendMessageSignals.m rename to Telegram/Watch/Bridge/TGBridgeSendMessageSignals.m diff --git a/Watch/Bridge/TGBridgeStateSignal.h b/Telegram/Watch/Bridge/TGBridgeStateSignal.h similarity index 100% rename from Watch/Bridge/TGBridgeStateSignal.h rename to Telegram/Watch/Bridge/TGBridgeStateSignal.h diff --git a/Watch/Bridge/TGBridgeStateSignal.m b/Telegram/Watch/Bridge/TGBridgeStateSignal.m similarity index 100% rename from Watch/Bridge/TGBridgeStateSignal.m rename to Telegram/Watch/Bridge/TGBridgeStateSignal.m diff --git a/Watch/Bridge/TGBridgeStickerPack.h b/Telegram/Watch/Bridge/TGBridgeStickerPack.h similarity index 100% rename from Watch/Bridge/TGBridgeStickerPack.h rename to Telegram/Watch/Bridge/TGBridgeStickerPack.h diff --git a/Watch/Bridge/TGBridgeStickerPack.m b/Telegram/Watch/Bridge/TGBridgeStickerPack.m similarity index 100% rename from Watch/Bridge/TGBridgeStickerPack.m rename to Telegram/Watch/Bridge/TGBridgeStickerPack.m diff --git a/Watch/Bridge/TGBridgeStickersSignals.h b/Telegram/Watch/Bridge/TGBridgeStickersSignals.h similarity index 100% rename from Watch/Bridge/TGBridgeStickersSignals.h rename to Telegram/Watch/Bridge/TGBridgeStickersSignals.h diff --git a/Watch/Bridge/TGBridgeStickersSignals.m b/Telegram/Watch/Bridge/TGBridgeStickersSignals.m similarity index 100% rename from Watch/Bridge/TGBridgeStickersSignals.m rename to Telegram/Watch/Bridge/TGBridgeStickersSignals.m diff --git a/Watch/Bridge/TGBridgeUser+TGTableItem.h b/Telegram/Watch/Bridge/TGBridgeUser+TGTableItem.h similarity index 100% rename from Watch/Bridge/TGBridgeUser+TGTableItem.h rename to Telegram/Watch/Bridge/TGBridgeUser+TGTableItem.h diff --git a/Watch/Bridge/TGBridgeUser+TGTableItem.m b/Telegram/Watch/Bridge/TGBridgeUser+TGTableItem.m similarity index 100% rename from Watch/Bridge/TGBridgeUser+TGTableItem.m rename to Telegram/Watch/Bridge/TGBridgeUser+TGTableItem.m diff --git a/Watch/Bridge/TGBridgeUserInfoSignals.h b/Telegram/Watch/Bridge/TGBridgeUserInfoSignals.h similarity index 100% rename from Watch/Bridge/TGBridgeUserInfoSignals.h rename to Telegram/Watch/Bridge/TGBridgeUserInfoSignals.h diff --git a/Watch/Bridge/TGBridgeUserInfoSignals.m b/Telegram/Watch/Bridge/TGBridgeUserInfoSignals.m similarity index 100% rename from Watch/Bridge/TGBridgeUserInfoSignals.m rename to Telegram/Watch/Bridge/TGBridgeUserInfoSignals.m diff --git a/Watch/Extension/Info.plist b/Telegram/Watch/Extension/Info.plist similarity index 100% rename from Watch/Extension/Info.plist rename to Telegram/Watch/Extension/Info.plist diff --git a/Watch/Extension/Resources/File@2x.png b/Telegram/Watch/Extension/Resources/File@2x.png similarity index 100% rename from Watch/Extension/Resources/File@2x.png rename to Telegram/Watch/Extension/Resources/File@2x.png diff --git a/Watch/Extension/Resources/Home88@2x.png b/Telegram/Watch/Extension/Resources/Home88@2x.png similarity index 100% rename from Watch/Extension/Resources/Home88@2x.png rename to Telegram/Watch/Extension/Resources/Home88@2x.png diff --git a/Watch/Extension/Resources/Location@2x.png b/Telegram/Watch/Extension/Resources/Location@2x.png similarity index 100% rename from Watch/Extension/Resources/Location@2x.png rename to Telegram/Watch/Extension/Resources/Location@2x.png diff --git a/Watch/Extension/Resources/MediaAudio@2x.png b/Telegram/Watch/Extension/Resources/MediaAudio@2x.png similarity index 100% rename from Watch/Extension/Resources/MediaAudio@2x.png rename to Telegram/Watch/Extension/Resources/MediaAudio@2x.png diff --git a/Watch/Extension/Resources/MediaDocument@2x.png b/Telegram/Watch/Extension/Resources/MediaDocument@2x.png similarity index 100% rename from Watch/Extension/Resources/MediaDocument@2x.png rename to Telegram/Watch/Extension/Resources/MediaDocument@2x.png diff --git a/Watch/Extension/Resources/MediaLocation@2x.png b/Telegram/Watch/Extension/Resources/MediaLocation@2x.png similarity index 100% rename from Watch/Extension/Resources/MediaLocation@2x.png rename to Telegram/Watch/Extension/Resources/MediaLocation@2x.png diff --git a/Watch/Extension/Resources/MediaPhoto@2x.png b/Telegram/Watch/Extension/Resources/MediaPhoto@2x.png similarity index 100% rename from Watch/Extension/Resources/MediaPhoto@2x.png rename to Telegram/Watch/Extension/Resources/MediaPhoto@2x.png diff --git a/Watch/Extension/Resources/MediaVideo@2x.png b/Telegram/Watch/Extension/Resources/MediaVideo@2x.png similarity index 100% rename from Watch/Extension/Resources/MediaVideo@2x.png rename to Telegram/Watch/Extension/Resources/MediaVideo@2x.png diff --git a/Watch/Extension/Resources/SavedMessagesAvatar@2x.png b/Telegram/Watch/Extension/Resources/SavedMessagesAvatar@2x.png similarity index 100% rename from Watch/Extension/Resources/SavedMessagesAvatar@2x.png rename to Telegram/Watch/Extension/Resources/SavedMessagesAvatar@2x.png diff --git a/Watch/Extension/Resources/VerifiedList@2x.png b/Telegram/Watch/Extension/Resources/VerifiedList@2x.png similarity index 100% rename from Watch/Extension/Resources/VerifiedList@2x.png rename to Telegram/Watch/Extension/Resources/VerifiedList@2x.png diff --git a/Watch/Extension/SSignalKit/SAtomic.h b/Telegram/Watch/Extension/SSignalKit/SAtomic.h similarity index 100% rename from Watch/Extension/SSignalKit/SAtomic.h rename to Telegram/Watch/Extension/SSignalKit/SAtomic.h diff --git a/Watch/Extension/SSignalKit/SAtomic.m b/Telegram/Watch/Extension/SSignalKit/SAtomic.m similarity index 100% rename from Watch/Extension/SSignalKit/SAtomic.m rename to Telegram/Watch/Extension/SSignalKit/SAtomic.m diff --git a/Watch/Extension/SSignalKit/SBag.h b/Telegram/Watch/Extension/SSignalKit/SBag.h similarity index 100% rename from Watch/Extension/SSignalKit/SBag.h rename to Telegram/Watch/Extension/SSignalKit/SBag.h diff --git a/Watch/Extension/SSignalKit/SBag.m b/Telegram/Watch/Extension/SSignalKit/SBag.m similarity index 100% rename from Watch/Extension/SSignalKit/SBag.m rename to Telegram/Watch/Extension/SSignalKit/SBag.m diff --git a/Watch/Extension/SSignalKit/SBlockDisposable.h b/Telegram/Watch/Extension/SSignalKit/SBlockDisposable.h similarity index 100% rename from Watch/Extension/SSignalKit/SBlockDisposable.h rename to Telegram/Watch/Extension/SSignalKit/SBlockDisposable.h diff --git a/Watch/Extension/SSignalKit/SBlockDisposable.m b/Telegram/Watch/Extension/SSignalKit/SBlockDisposable.m similarity index 100% rename from Watch/Extension/SSignalKit/SBlockDisposable.m rename to Telegram/Watch/Extension/SSignalKit/SBlockDisposable.m diff --git a/Watch/Extension/SSignalKit/SDisposable.h b/Telegram/Watch/Extension/SSignalKit/SDisposable.h similarity index 100% rename from Watch/Extension/SSignalKit/SDisposable.h rename to Telegram/Watch/Extension/SSignalKit/SDisposable.h diff --git a/Watch/Extension/SSignalKit/SDisposableSet.h b/Telegram/Watch/Extension/SSignalKit/SDisposableSet.h similarity index 100% rename from Watch/Extension/SSignalKit/SDisposableSet.h rename to Telegram/Watch/Extension/SSignalKit/SDisposableSet.h diff --git a/Watch/Extension/SSignalKit/SDisposableSet.m b/Telegram/Watch/Extension/SSignalKit/SDisposableSet.m similarity index 100% rename from Watch/Extension/SSignalKit/SDisposableSet.m rename to Telegram/Watch/Extension/SSignalKit/SDisposableSet.m diff --git a/Watch/Extension/SSignalKit/SMetaDisposable.h b/Telegram/Watch/Extension/SSignalKit/SMetaDisposable.h similarity index 100% rename from Watch/Extension/SSignalKit/SMetaDisposable.h rename to Telegram/Watch/Extension/SSignalKit/SMetaDisposable.h diff --git a/Watch/Extension/SSignalKit/SMetaDisposable.m b/Telegram/Watch/Extension/SSignalKit/SMetaDisposable.m similarity index 100% rename from Watch/Extension/SSignalKit/SMetaDisposable.m rename to Telegram/Watch/Extension/SSignalKit/SMetaDisposable.m diff --git a/Watch/Extension/SSignalKit/SMulticastSignalManager.h b/Telegram/Watch/Extension/SSignalKit/SMulticastSignalManager.h similarity index 100% rename from Watch/Extension/SSignalKit/SMulticastSignalManager.h rename to Telegram/Watch/Extension/SSignalKit/SMulticastSignalManager.h diff --git a/Watch/Extension/SSignalKit/SMulticastSignalManager.m b/Telegram/Watch/Extension/SSignalKit/SMulticastSignalManager.m similarity index 100% rename from Watch/Extension/SSignalKit/SMulticastSignalManager.m rename to Telegram/Watch/Extension/SSignalKit/SMulticastSignalManager.m diff --git a/Watch/Extension/SSignalKit/SQueue.h b/Telegram/Watch/Extension/SSignalKit/SQueue.h similarity index 100% rename from Watch/Extension/SSignalKit/SQueue.h rename to Telegram/Watch/Extension/SSignalKit/SQueue.h diff --git a/Watch/Extension/SSignalKit/SQueue.m b/Telegram/Watch/Extension/SSignalKit/SQueue.m similarity index 100% rename from Watch/Extension/SSignalKit/SQueue.m rename to Telegram/Watch/Extension/SSignalKit/SQueue.m diff --git a/Watch/Extension/SSignalKit/SSignal+Accumulate.h b/Telegram/Watch/Extension/SSignalKit/SSignal+Accumulate.h similarity index 100% rename from Watch/Extension/SSignalKit/SSignal+Accumulate.h rename to Telegram/Watch/Extension/SSignalKit/SSignal+Accumulate.h diff --git a/Watch/Extension/SSignalKit/SSignal+Accumulate.m b/Telegram/Watch/Extension/SSignalKit/SSignal+Accumulate.m similarity index 100% rename from Watch/Extension/SSignalKit/SSignal+Accumulate.m rename to Telegram/Watch/Extension/SSignalKit/SSignal+Accumulate.m diff --git a/Watch/Extension/SSignalKit/SSignal+Catch.h b/Telegram/Watch/Extension/SSignalKit/SSignal+Catch.h similarity index 100% rename from Watch/Extension/SSignalKit/SSignal+Catch.h rename to Telegram/Watch/Extension/SSignalKit/SSignal+Catch.h diff --git a/Watch/Extension/SSignalKit/SSignal+Catch.m b/Telegram/Watch/Extension/SSignalKit/SSignal+Catch.m similarity index 100% rename from Watch/Extension/SSignalKit/SSignal+Catch.m rename to Telegram/Watch/Extension/SSignalKit/SSignal+Catch.m diff --git a/Watch/Extension/SSignalKit/SSignal+Combine.h b/Telegram/Watch/Extension/SSignalKit/SSignal+Combine.h similarity index 100% rename from Watch/Extension/SSignalKit/SSignal+Combine.h rename to Telegram/Watch/Extension/SSignalKit/SSignal+Combine.h diff --git a/Watch/Extension/SSignalKit/SSignal+Combine.m b/Telegram/Watch/Extension/SSignalKit/SSignal+Combine.m similarity index 100% rename from Watch/Extension/SSignalKit/SSignal+Combine.m rename to Telegram/Watch/Extension/SSignalKit/SSignal+Combine.m diff --git a/Watch/Extension/SSignalKit/SSignal+Dispatch.h b/Telegram/Watch/Extension/SSignalKit/SSignal+Dispatch.h similarity index 100% rename from Watch/Extension/SSignalKit/SSignal+Dispatch.h rename to Telegram/Watch/Extension/SSignalKit/SSignal+Dispatch.h diff --git a/Watch/Extension/SSignalKit/SSignal+Dispatch.m b/Telegram/Watch/Extension/SSignalKit/SSignal+Dispatch.m similarity index 100% rename from Watch/Extension/SSignalKit/SSignal+Dispatch.m rename to Telegram/Watch/Extension/SSignalKit/SSignal+Dispatch.m diff --git a/Watch/Extension/SSignalKit/SSignal+Mapping.h b/Telegram/Watch/Extension/SSignalKit/SSignal+Mapping.h similarity index 100% rename from Watch/Extension/SSignalKit/SSignal+Mapping.h rename to Telegram/Watch/Extension/SSignalKit/SSignal+Mapping.h diff --git a/Watch/Extension/SSignalKit/SSignal+Mapping.m b/Telegram/Watch/Extension/SSignalKit/SSignal+Mapping.m similarity index 100% rename from Watch/Extension/SSignalKit/SSignal+Mapping.m rename to Telegram/Watch/Extension/SSignalKit/SSignal+Mapping.m diff --git a/Watch/Extension/SSignalKit/SSignal+Meta.h b/Telegram/Watch/Extension/SSignalKit/SSignal+Meta.h similarity index 100% rename from Watch/Extension/SSignalKit/SSignal+Meta.h rename to Telegram/Watch/Extension/SSignalKit/SSignal+Meta.h diff --git a/Watch/Extension/SSignalKit/SSignal+Meta.m b/Telegram/Watch/Extension/SSignalKit/SSignal+Meta.m similarity index 100% rename from Watch/Extension/SSignalKit/SSignal+Meta.m rename to Telegram/Watch/Extension/SSignalKit/SSignal+Meta.m diff --git a/Watch/Extension/SSignalKit/SSignal+Multicast.h b/Telegram/Watch/Extension/SSignalKit/SSignal+Multicast.h similarity index 100% rename from Watch/Extension/SSignalKit/SSignal+Multicast.h rename to Telegram/Watch/Extension/SSignalKit/SSignal+Multicast.h diff --git a/Watch/Extension/SSignalKit/SSignal+Multicast.m b/Telegram/Watch/Extension/SSignalKit/SSignal+Multicast.m similarity index 100% rename from Watch/Extension/SSignalKit/SSignal+Multicast.m rename to Telegram/Watch/Extension/SSignalKit/SSignal+Multicast.m diff --git a/Watch/Extension/SSignalKit/SSignal+Pipe.h b/Telegram/Watch/Extension/SSignalKit/SSignal+Pipe.h similarity index 100% rename from Watch/Extension/SSignalKit/SSignal+Pipe.h rename to Telegram/Watch/Extension/SSignalKit/SSignal+Pipe.h diff --git a/Watch/Extension/SSignalKit/SSignal+Pipe.m b/Telegram/Watch/Extension/SSignalKit/SSignal+Pipe.m similarity index 100% rename from Watch/Extension/SSignalKit/SSignal+Pipe.m rename to Telegram/Watch/Extension/SSignalKit/SSignal+Pipe.m diff --git a/Watch/Extension/SSignalKit/SSignal+SideEffects.h b/Telegram/Watch/Extension/SSignalKit/SSignal+SideEffects.h similarity index 100% rename from Watch/Extension/SSignalKit/SSignal+SideEffects.h rename to Telegram/Watch/Extension/SSignalKit/SSignal+SideEffects.h diff --git a/Watch/Extension/SSignalKit/SSignal+SideEffects.m b/Telegram/Watch/Extension/SSignalKit/SSignal+SideEffects.m similarity index 100% rename from Watch/Extension/SSignalKit/SSignal+SideEffects.m rename to Telegram/Watch/Extension/SSignalKit/SSignal+SideEffects.m diff --git a/Watch/Extension/SSignalKit/SSignal+Single.h b/Telegram/Watch/Extension/SSignalKit/SSignal+Single.h similarity index 100% rename from Watch/Extension/SSignalKit/SSignal+Single.h rename to Telegram/Watch/Extension/SSignalKit/SSignal+Single.h diff --git a/Watch/Extension/SSignalKit/SSignal+Single.m b/Telegram/Watch/Extension/SSignalKit/SSignal+Single.m similarity index 100% rename from Watch/Extension/SSignalKit/SSignal+Single.m rename to Telegram/Watch/Extension/SSignalKit/SSignal+Single.m diff --git a/Watch/Extension/SSignalKit/SSignal+Take.h b/Telegram/Watch/Extension/SSignalKit/SSignal+Take.h similarity index 100% rename from Watch/Extension/SSignalKit/SSignal+Take.h rename to Telegram/Watch/Extension/SSignalKit/SSignal+Take.h diff --git a/Watch/Extension/SSignalKit/SSignal+Take.m b/Telegram/Watch/Extension/SSignalKit/SSignal+Take.m similarity index 100% rename from Watch/Extension/SSignalKit/SSignal+Take.m rename to Telegram/Watch/Extension/SSignalKit/SSignal+Take.m diff --git a/Watch/Extension/SSignalKit/SSignal+Timing.h b/Telegram/Watch/Extension/SSignalKit/SSignal+Timing.h similarity index 100% rename from Watch/Extension/SSignalKit/SSignal+Timing.h rename to Telegram/Watch/Extension/SSignalKit/SSignal+Timing.h diff --git a/Watch/Extension/SSignalKit/SSignal+Timing.m b/Telegram/Watch/Extension/SSignalKit/SSignal+Timing.m similarity index 100% rename from Watch/Extension/SSignalKit/SSignal+Timing.m rename to Telegram/Watch/Extension/SSignalKit/SSignal+Timing.m diff --git a/Watch/Extension/SSignalKit/SSignal.h b/Telegram/Watch/Extension/SSignalKit/SSignal.h similarity index 100% rename from Watch/Extension/SSignalKit/SSignal.h rename to Telegram/Watch/Extension/SSignalKit/SSignal.h diff --git a/Watch/Extension/SSignalKit/SSignal.m b/Telegram/Watch/Extension/SSignalKit/SSignal.m similarity index 100% rename from Watch/Extension/SSignalKit/SSignal.m rename to Telegram/Watch/Extension/SSignalKit/SSignal.m diff --git a/Watch/Extension/SSignalKit/SSignalKit.h b/Telegram/Watch/Extension/SSignalKit/SSignalKit.h similarity index 100% rename from Watch/Extension/SSignalKit/SSignalKit.h rename to Telegram/Watch/Extension/SSignalKit/SSignalKit.h diff --git a/Watch/Extension/SSignalKit/SSubscriber.h b/Telegram/Watch/Extension/SSignalKit/SSubscriber.h similarity index 100% rename from Watch/Extension/SSignalKit/SSubscriber.h rename to Telegram/Watch/Extension/SSignalKit/SSubscriber.h diff --git a/Watch/Extension/SSignalKit/SSubscriber.m b/Telegram/Watch/Extension/SSignalKit/SSubscriber.m similarity index 100% rename from Watch/Extension/SSignalKit/SSubscriber.m rename to Telegram/Watch/Extension/SSignalKit/SSubscriber.m diff --git a/Watch/Extension/SSignalKit/SThreadPool.h b/Telegram/Watch/Extension/SSignalKit/SThreadPool.h similarity index 100% rename from Watch/Extension/SSignalKit/SThreadPool.h rename to Telegram/Watch/Extension/SSignalKit/SThreadPool.h diff --git a/Watch/Extension/SSignalKit/SThreadPool.m b/Telegram/Watch/Extension/SSignalKit/SThreadPool.m similarity index 100% rename from Watch/Extension/SSignalKit/SThreadPool.m rename to Telegram/Watch/Extension/SSignalKit/SThreadPool.m diff --git a/Watch/Extension/SSignalKit/SThreadPoolQueue.h b/Telegram/Watch/Extension/SSignalKit/SThreadPoolQueue.h similarity index 100% rename from Watch/Extension/SSignalKit/SThreadPoolQueue.h rename to Telegram/Watch/Extension/SSignalKit/SThreadPoolQueue.h diff --git a/Watch/Extension/SSignalKit/SThreadPoolQueue.m b/Telegram/Watch/Extension/SSignalKit/SThreadPoolQueue.m similarity index 100% rename from Watch/Extension/SSignalKit/SThreadPoolQueue.m rename to Telegram/Watch/Extension/SSignalKit/SThreadPoolQueue.m diff --git a/Watch/Extension/SSignalKit/SThreadPoolTask.h b/Telegram/Watch/Extension/SSignalKit/SThreadPoolTask.h similarity index 100% rename from Watch/Extension/SSignalKit/SThreadPoolTask.h rename to Telegram/Watch/Extension/SSignalKit/SThreadPoolTask.h diff --git a/Watch/Extension/SSignalKit/SThreadPoolTask.m b/Telegram/Watch/Extension/SSignalKit/SThreadPoolTask.m similarity index 100% rename from Watch/Extension/SSignalKit/SThreadPoolTask.m rename to Telegram/Watch/Extension/SSignalKit/SThreadPoolTask.m diff --git a/Watch/Extension/SSignalKit/STimer.h b/Telegram/Watch/Extension/SSignalKit/STimer.h similarity index 100% rename from Watch/Extension/SSignalKit/STimer.h rename to Telegram/Watch/Extension/SSignalKit/STimer.h diff --git a/Watch/Extension/SSignalKit/STimer.m b/Telegram/Watch/Extension/SSignalKit/STimer.m similarity index 100% rename from Watch/Extension/SSignalKit/STimer.m rename to Telegram/Watch/Extension/SSignalKit/STimer.m diff --git a/Watch/Extension/SSignalKit/SVariable.h b/Telegram/Watch/Extension/SSignalKit/SVariable.h similarity index 100% rename from Watch/Extension/SSignalKit/SVariable.h rename to Telegram/Watch/Extension/SSignalKit/SVariable.h diff --git a/Watch/Extension/SSignalKit/SVariable.m b/Telegram/Watch/Extension/SSignalKit/SVariable.m similarity index 100% rename from Watch/Extension/SSignalKit/SVariable.m rename to Telegram/Watch/Extension/SSignalKit/SVariable.m diff --git a/Watch/Extension/TGAudioMicAlertController.h b/Telegram/Watch/Extension/TGAudioMicAlertController.h similarity index 100% rename from Watch/Extension/TGAudioMicAlertController.h rename to Telegram/Watch/Extension/TGAudioMicAlertController.h diff --git a/Watch/Extension/TGAudioMicAlertController.m b/Telegram/Watch/Extension/TGAudioMicAlertController.m similarity index 100% rename from Watch/Extension/TGAudioMicAlertController.m rename to Telegram/Watch/Extension/TGAudioMicAlertController.m diff --git a/Watch/Extension/TGAvatarViewModel.h b/Telegram/Watch/Extension/TGAvatarViewModel.h similarity index 100% rename from Watch/Extension/TGAvatarViewModel.h rename to Telegram/Watch/Extension/TGAvatarViewModel.h diff --git a/Watch/Extension/TGAvatarViewModel.m b/Telegram/Watch/Extension/TGAvatarViewModel.m similarity index 100% rename from Watch/Extension/TGAvatarViewModel.m rename to Telegram/Watch/Extension/TGAvatarViewModel.m diff --git a/Watch/Extension/TGBotCommandController.h b/Telegram/Watch/Extension/TGBotCommandController.h similarity index 100% rename from Watch/Extension/TGBotCommandController.h rename to Telegram/Watch/Extension/TGBotCommandController.h diff --git a/Watch/Extension/TGBotCommandController.m b/Telegram/Watch/Extension/TGBotCommandController.m similarity index 100% rename from Watch/Extension/TGBotCommandController.m rename to Telegram/Watch/Extension/TGBotCommandController.m diff --git a/Watch/Extension/TGBotKeyboardButtonController.h b/Telegram/Watch/Extension/TGBotKeyboardButtonController.h similarity index 100% rename from Watch/Extension/TGBotKeyboardButtonController.h rename to Telegram/Watch/Extension/TGBotKeyboardButtonController.h diff --git a/Watch/Extension/TGBotKeyboardButtonController.m b/Telegram/Watch/Extension/TGBotKeyboardButtonController.m similarity index 100% rename from Watch/Extension/TGBotKeyboardButtonController.m rename to Telegram/Watch/Extension/TGBotKeyboardButtonController.m diff --git a/Watch/Extension/TGBotKeyboardController.h b/Telegram/Watch/Extension/TGBotKeyboardController.h similarity index 100% rename from Watch/Extension/TGBotKeyboardController.h rename to Telegram/Watch/Extension/TGBotKeyboardController.h diff --git a/Watch/Extension/TGBotKeyboardController.m b/Telegram/Watch/Extension/TGBotKeyboardController.m similarity index 100% rename from Watch/Extension/TGBotKeyboardController.m rename to Telegram/Watch/Extension/TGBotKeyboardController.m diff --git a/Watch/Extension/TGBridgeUserCache.h b/Telegram/Watch/Extension/TGBridgeUserCache.h similarity index 100% rename from Watch/Extension/TGBridgeUserCache.h rename to Telegram/Watch/Extension/TGBridgeUserCache.h diff --git a/Watch/Extension/TGBridgeUserCache.m b/Telegram/Watch/Extension/TGBridgeUserCache.m similarity index 100% rename from Watch/Extension/TGBridgeUserCache.m rename to Telegram/Watch/Extension/TGBridgeUserCache.m diff --git a/Watch/Extension/TGChatInfo.h b/Telegram/Watch/Extension/TGChatInfo.h similarity index 100% rename from Watch/Extension/TGChatInfo.h rename to Telegram/Watch/Extension/TGChatInfo.h diff --git a/Watch/Extension/TGChatInfo.m b/Telegram/Watch/Extension/TGChatInfo.m similarity index 100% rename from Watch/Extension/TGChatInfo.m rename to Telegram/Watch/Extension/TGChatInfo.m diff --git a/Watch/Extension/TGChatTimestamp.h b/Telegram/Watch/Extension/TGChatTimestamp.h similarity index 100% rename from Watch/Extension/TGChatTimestamp.h rename to Telegram/Watch/Extension/TGChatTimestamp.h diff --git a/Watch/Extension/TGChatTimestamp.m b/Telegram/Watch/Extension/TGChatTimestamp.m similarity index 100% rename from Watch/Extension/TGChatTimestamp.m rename to Telegram/Watch/Extension/TGChatTimestamp.m diff --git a/Watch/Extension/TGComplicationController.h b/Telegram/Watch/Extension/TGComplicationController.h similarity index 100% rename from Watch/Extension/TGComplicationController.h rename to Telegram/Watch/Extension/TGComplicationController.h diff --git a/Watch/Extension/TGComplicationController.m b/Telegram/Watch/Extension/TGComplicationController.m similarity index 100% rename from Watch/Extension/TGComplicationController.m rename to Telegram/Watch/Extension/TGComplicationController.m diff --git a/Watch/Extension/TGComposeController.h b/Telegram/Watch/Extension/TGComposeController.h similarity index 100% rename from Watch/Extension/TGComposeController.h rename to Telegram/Watch/Extension/TGComposeController.h diff --git a/Watch/Extension/TGComposeController.m b/Telegram/Watch/Extension/TGComposeController.m similarity index 100% rename from Watch/Extension/TGComposeController.m rename to Telegram/Watch/Extension/TGComposeController.m diff --git a/Watch/Extension/TGContactsController.h b/Telegram/Watch/Extension/TGContactsController.h similarity index 100% rename from Watch/Extension/TGContactsController.h rename to Telegram/Watch/Extension/TGContactsController.h diff --git a/Watch/Extension/TGContactsController.m b/Telegram/Watch/Extension/TGContactsController.m similarity index 100% rename from Watch/Extension/TGContactsController.m rename to Telegram/Watch/Extension/TGContactsController.m diff --git a/Watch/Extension/TGConversationFooterController.h b/Telegram/Watch/Extension/TGConversationFooterController.h similarity index 100% rename from Watch/Extension/TGConversationFooterController.h rename to Telegram/Watch/Extension/TGConversationFooterController.h diff --git a/Watch/Extension/TGConversationFooterController.m b/Telegram/Watch/Extension/TGConversationFooterController.m similarity index 100% rename from Watch/Extension/TGConversationFooterController.m rename to Telegram/Watch/Extension/TGConversationFooterController.m diff --git a/Watch/Extension/TGDateUtils.h b/Telegram/Watch/Extension/TGDateUtils.h similarity index 100% rename from Watch/Extension/TGDateUtils.h rename to Telegram/Watch/Extension/TGDateUtils.h diff --git a/Watch/Extension/TGDateUtils.m b/Telegram/Watch/Extension/TGDateUtils.m similarity index 100% rename from Watch/Extension/TGDateUtils.m rename to Telegram/Watch/Extension/TGDateUtils.m diff --git a/Watch/Extension/TGExtensionDelegate.h b/Telegram/Watch/Extension/TGExtensionDelegate.h similarity index 100% rename from Watch/Extension/TGExtensionDelegate.h rename to Telegram/Watch/Extension/TGExtensionDelegate.h diff --git a/Watch/Extension/TGExtensionDelegate.m b/Telegram/Watch/Extension/TGExtensionDelegate.m similarity index 100% rename from Watch/Extension/TGExtensionDelegate.m rename to Telegram/Watch/Extension/TGExtensionDelegate.m diff --git a/Watch/Extension/TGFileCache.h b/Telegram/Watch/Extension/TGFileCache.h similarity index 100% rename from Watch/Extension/TGFileCache.h rename to Telegram/Watch/Extension/TGFileCache.h diff --git a/Watch/Extension/TGFileCache.m b/Telegram/Watch/Extension/TGFileCache.m similarity index 100% rename from Watch/Extension/TGFileCache.m rename to Telegram/Watch/Extension/TGFileCache.m diff --git a/Watch/Extension/TGGeometry.h b/Telegram/Watch/Extension/TGGeometry.h similarity index 100% rename from Watch/Extension/TGGeometry.h rename to Telegram/Watch/Extension/TGGeometry.h diff --git a/Watch/Extension/TGGeometry.m b/Telegram/Watch/Extension/TGGeometry.m similarity index 100% rename from Watch/Extension/TGGeometry.m rename to Telegram/Watch/Extension/TGGeometry.m diff --git a/Watch/Extension/TGGroupInfoController.h b/Telegram/Watch/Extension/TGGroupInfoController.h similarity index 100% rename from Watch/Extension/TGGroupInfoController.h rename to Telegram/Watch/Extension/TGGroupInfoController.h diff --git a/Watch/Extension/TGGroupInfoController.m b/Telegram/Watch/Extension/TGGroupInfoController.m similarity index 100% rename from Watch/Extension/TGGroupInfoController.m rename to Telegram/Watch/Extension/TGGroupInfoController.m diff --git a/Watch/Extension/TGGroupInfoFooterController.h b/Telegram/Watch/Extension/TGGroupInfoFooterController.h similarity index 100% rename from Watch/Extension/TGGroupInfoFooterController.h rename to Telegram/Watch/Extension/TGGroupInfoFooterController.h diff --git a/Watch/Extension/TGGroupInfoFooterController.m b/Telegram/Watch/Extension/TGGroupInfoFooterController.m similarity index 100% rename from Watch/Extension/TGGroupInfoFooterController.m rename to Telegram/Watch/Extension/TGGroupInfoFooterController.m diff --git a/Watch/Extension/TGGroupInfoHeaderController.h b/Telegram/Watch/Extension/TGGroupInfoHeaderController.h similarity index 100% rename from Watch/Extension/TGGroupInfoHeaderController.h rename to Telegram/Watch/Extension/TGGroupInfoHeaderController.h diff --git a/Watch/Extension/TGGroupInfoHeaderController.m b/Telegram/Watch/Extension/TGGroupInfoHeaderController.m similarity index 100% rename from Watch/Extension/TGGroupInfoHeaderController.m rename to Telegram/Watch/Extension/TGGroupInfoHeaderController.m diff --git a/Watch/Extension/TGIndexPath.h b/Telegram/Watch/Extension/TGIndexPath.h similarity index 100% rename from Watch/Extension/TGIndexPath.h rename to Telegram/Watch/Extension/TGIndexPath.h diff --git a/Watch/Extension/TGIndexPath.m b/Telegram/Watch/Extension/TGIndexPath.m similarity index 100% rename from Watch/Extension/TGIndexPath.m rename to Telegram/Watch/Extension/TGIndexPath.m diff --git a/Watch/Extension/TGInputController.h b/Telegram/Watch/Extension/TGInputController.h similarity index 100% rename from Watch/Extension/TGInputController.h rename to Telegram/Watch/Extension/TGInputController.h diff --git a/Watch/Extension/TGInputController.m b/Telegram/Watch/Extension/TGInputController.m similarity index 100% rename from Watch/Extension/TGInputController.m rename to Telegram/Watch/Extension/TGInputController.m diff --git a/Watch/Extension/TGInterfaceController.h b/Telegram/Watch/Extension/TGInterfaceController.h similarity index 100% rename from Watch/Extension/TGInterfaceController.h rename to Telegram/Watch/Extension/TGInterfaceController.h diff --git a/Watch/Extension/TGInterfaceController.m b/Telegram/Watch/Extension/TGInterfaceController.m similarity index 100% rename from Watch/Extension/TGInterfaceController.m rename to Telegram/Watch/Extension/TGInterfaceController.m diff --git a/Watch/Extension/TGInterfaceMenu.h b/Telegram/Watch/Extension/TGInterfaceMenu.h similarity index 100% rename from Watch/Extension/TGInterfaceMenu.h rename to Telegram/Watch/Extension/TGInterfaceMenu.h diff --git a/Watch/Extension/TGInterfaceMenu.m b/Telegram/Watch/Extension/TGInterfaceMenu.m similarity index 100% rename from Watch/Extension/TGInterfaceMenu.m rename to Telegram/Watch/Extension/TGInterfaceMenu.m diff --git a/Watch/Extension/TGLocationController.h b/Telegram/Watch/Extension/TGLocationController.h similarity index 100% rename from Watch/Extension/TGLocationController.h rename to Telegram/Watch/Extension/TGLocationController.h diff --git a/Watch/Extension/TGLocationController.m b/Telegram/Watch/Extension/TGLocationController.m similarity index 100% rename from Watch/Extension/TGLocationController.m rename to Telegram/Watch/Extension/TGLocationController.m diff --git a/Watch/Extension/TGLocationMapHeaderController.h b/Telegram/Watch/Extension/TGLocationMapHeaderController.h similarity index 100% rename from Watch/Extension/TGLocationMapHeaderController.h rename to Telegram/Watch/Extension/TGLocationMapHeaderController.h diff --git a/Watch/Extension/TGLocationMapHeaderController.m b/Telegram/Watch/Extension/TGLocationMapHeaderController.m similarity index 100% rename from Watch/Extension/TGLocationMapHeaderController.m rename to Telegram/Watch/Extension/TGLocationMapHeaderController.m diff --git a/Watch/Extension/TGLocationUtils.h b/Telegram/Watch/Extension/TGLocationUtils.h similarity index 100% rename from Watch/Extension/TGLocationUtils.h rename to Telegram/Watch/Extension/TGLocationUtils.h diff --git a/Watch/Extension/TGLocationUtils.m b/Telegram/Watch/Extension/TGLocationUtils.m similarity index 100% rename from Watch/Extension/TGLocationUtils.m rename to Telegram/Watch/Extension/TGLocationUtils.m diff --git a/Watch/Extension/TGLocationVenueRowController.h b/Telegram/Watch/Extension/TGLocationVenueRowController.h similarity index 100% rename from Watch/Extension/TGLocationVenueRowController.h rename to Telegram/Watch/Extension/TGLocationVenueRowController.h diff --git a/Watch/Extension/TGLocationVenueRowController.m b/Telegram/Watch/Extension/TGLocationVenueRowController.m similarity index 100% rename from Watch/Extension/TGLocationVenueRowController.m rename to Telegram/Watch/Extension/TGLocationVenueRowController.m diff --git a/Watch/Extension/TGMessageViewController.h b/Telegram/Watch/Extension/TGMessageViewController.h similarity index 100% rename from Watch/Extension/TGMessageViewController.h rename to Telegram/Watch/Extension/TGMessageViewController.h diff --git a/Watch/Extension/TGMessageViewController.m b/Telegram/Watch/Extension/TGMessageViewController.m similarity index 100% rename from Watch/Extension/TGMessageViewController.m rename to Telegram/Watch/Extension/TGMessageViewController.m diff --git a/Watch/Extension/TGMessageViewFooterController.h b/Telegram/Watch/Extension/TGMessageViewFooterController.h similarity index 100% rename from Watch/Extension/TGMessageViewFooterController.h rename to Telegram/Watch/Extension/TGMessageViewFooterController.h diff --git a/Watch/Extension/TGMessageViewFooterController.m b/Telegram/Watch/Extension/TGMessageViewFooterController.m similarity index 100% rename from Watch/Extension/TGMessageViewFooterController.m rename to Telegram/Watch/Extension/TGMessageViewFooterController.m diff --git a/Watch/Extension/TGMessageViewMessageRowController.h b/Telegram/Watch/Extension/TGMessageViewMessageRowController.h similarity index 100% rename from Watch/Extension/TGMessageViewMessageRowController.h rename to Telegram/Watch/Extension/TGMessageViewMessageRowController.h diff --git a/Watch/Extension/TGMessageViewMessageRowController.m b/Telegram/Watch/Extension/TGMessageViewMessageRowController.m similarity index 100% rename from Watch/Extension/TGMessageViewMessageRowController.m rename to Telegram/Watch/Extension/TGMessageViewMessageRowController.m diff --git a/Watch/Extension/TGMessageViewModel.h b/Telegram/Watch/Extension/TGMessageViewModel.h similarity index 100% rename from Watch/Extension/TGMessageViewModel.h rename to Telegram/Watch/Extension/TGMessageViewModel.h diff --git a/Watch/Extension/TGMessageViewModel.m b/Telegram/Watch/Extension/TGMessageViewModel.m similarity index 100% rename from Watch/Extension/TGMessageViewModel.m rename to Telegram/Watch/Extension/TGMessageViewModel.m diff --git a/Watch/Extension/TGMessageViewWebPageRowController.h b/Telegram/Watch/Extension/TGMessageViewWebPageRowController.h similarity index 100% rename from Watch/Extension/TGMessageViewWebPageRowController.h rename to Telegram/Watch/Extension/TGMessageViewWebPageRowController.h diff --git a/Watch/Extension/TGMessageViewWebPageRowController.m b/Telegram/Watch/Extension/TGMessageViewWebPageRowController.m similarity index 100% rename from Watch/Extension/TGMessageViewWebPageRowController.m rename to Telegram/Watch/Extension/TGMessageViewWebPageRowController.m diff --git a/Watch/Extension/TGNeoAttachmentViewModel.h b/Telegram/Watch/Extension/TGNeoAttachmentViewModel.h similarity index 100% rename from Watch/Extension/TGNeoAttachmentViewModel.h rename to Telegram/Watch/Extension/TGNeoAttachmentViewModel.h diff --git a/Watch/Extension/TGNeoAttachmentViewModel.m b/Telegram/Watch/Extension/TGNeoAttachmentViewModel.m similarity index 100% rename from Watch/Extension/TGNeoAttachmentViewModel.m rename to Telegram/Watch/Extension/TGNeoAttachmentViewModel.m diff --git a/Watch/Extension/TGNeoAudioMessageViewModel.h b/Telegram/Watch/Extension/TGNeoAudioMessageViewModel.h similarity index 100% rename from Watch/Extension/TGNeoAudioMessageViewModel.h rename to Telegram/Watch/Extension/TGNeoAudioMessageViewModel.h diff --git a/Watch/Extension/TGNeoAudioMessageViewModel.m b/Telegram/Watch/Extension/TGNeoAudioMessageViewModel.m similarity index 100% rename from Watch/Extension/TGNeoAudioMessageViewModel.m rename to Telegram/Watch/Extension/TGNeoAudioMessageViewModel.m diff --git a/Watch/Extension/TGNeoBackgroundViewModel.h b/Telegram/Watch/Extension/TGNeoBackgroundViewModel.h similarity index 100% rename from Watch/Extension/TGNeoBackgroundViewModel.h rename to Telegram/Watch/Extension/TGNeoBackgroundViewModel.h diff --git a/Watch/Extension/TGNeoBackgroundViewModel.m b/Telegram/Watch/Extension/TGNeoBackgroundViewModel.m similarity index 100% rename from Watch/Extension/TGNeoBackgroundViewModel.m rename to Telegram/Watch/Extension/TGNeoBackgroundViewModel.m diff --git a/Watch/Extension/TGNeoBubbleMessageViewModel.h b/Telegram/Watch/Extension/TGNeoBubbleMessageViewModel.h similarity index 100% rename from Watch/Extension/TGNeoBubbleMessageViewModel.h rename to Telegram/Watch/Extension/TGNeoBubbleMessageViewModel.h diff --git a/Watch/Extension/TGNeoBubbleMessageViewModel.m b/Telegram/Watch/Extension/TGNeoBubbleMessageViewModel.m similarity index 100% rename from Watch/Extension/TGNeoBubbleMessageViewModel.m rename to Telegram/Watch/Extension/TGNeoBubbleMessageViewModel.m diff --git a/Watch/Extension/TGNeoChatRowController.h b/Telegram/Watch/Extension/TGNeoChatRowController.h similarity index 100% rename from Watch/Extension/TGNeoChatRowController.h rename to Telegram/Watch/Extension/TGNeoChatRowController.h diff --git a/Watch/Extension/TGNeoChatRowController.m b/Telegram/Watch/Extension/TGNeoChatRowController.m similarity index 100% rename from Watch/Extension/TGNeoChatRowController.m rename to Telegram/Watch/Extension/TGNeoChatRowController.m diff --git a/Watch/Extension/TGNeoChatViewModel.h b/Telegram/Watch/Extension/TGNeoChatViewModel.h similarity index 100% rename from Watch/Extension/TGNeoChatViewModel.h rename to Telegram/Watch/Extension/TGNeoChatViewModel.h diff --git a/Watch/Extension/TGNeoChatViewModel.m b/Telegram/Watch/Extension/TGNeoChatViewModel.m similarity index 100% rename from Watch/Extension/TGNeoChatViewModel.m rename to Telegram/Watch/Extension/TGNeoChatViewModel.m diff --git a/Watch/Extension/TGNeoChatsController.h b/Telegram/Watch/Extension/TGNeoChatsController.h similarity index 100% rename from Watch/Extension/TGNeoChatsController.h rename to Telegram/Watch/Extension/TGNeoChatsController.h diff --git a/Watch/Extension/TGNeoChatsController.m b/Telegram/Watch/Extension/TGNeoChatsController.m similarity index 100% rename from Watch/Extension/TGNeoChatsController.m rename to Telegram/Watch/Extension/TGNeoChatsController.m diff --git a/Watch/Extension/TGNeoContactMessageViewModel.h b/Telegram/Watch/Extension/TGNeoContactMessageViewModel.h similarity index 100% rename from Watch/Extension/TGNeoContactMessageViewModel.h rename to Telegram/Watch/Extension/TGNeoContactMessageViewModel.h diff --git a/Watch/Extension/TGNeoContactMessageViewModel.m b/Telegram/Watch/Extension/TGNeoContactMessageViewModel.m similarity index 100% rename from Watch/Extension/TGNeoContactMessageViewModel.m rename to Telegram/Watch/Extension/TGNeoContactMessageViewModel.m diff --git a/Watch/Extension/TGNeoConversationController.h b/Telegram/Watch/Extension/TGNeoConversationController.h similarity index 100% rename from Watch/Extension/TGNeoConversationController.h rename to Telegram/Watch/Extension/TGNeoConversationController.h diff --git a/Watch/Extension/TGNeoConversationController.m b/Telegram/Watch/Extension/TGNeoConversationController.m similarity index 100% rename from Watch/Extension/TGNeoConversationController.m rename to Telegram/Watch/Extension/TGNeoConversationController.m diff --git a/Watch/Extension/TGNeoConversationMediaRowController.h b/Telegram/Watch/Extension/TGNeoConversationMediaRowController.h similarity index 100% rename from Watch/Extension/TGNeoConversationMediaRowController.h rename to Telegram/Watch/Extension/TGNeoConversationMediaRowController.h diff --git a/Watch/Extension/TGNeoConversationMediaRowController.m b/Telegram/Watch/Extension/TGNeoConversationMediaRowController.m similarity index 100% rename from Watch/Extension/TGNeoConversationMediaRowController.m rename to Telegram/Watch/Extension/TGNeoConversationMediaRowController.m diff --git a/Watch/Extension/TGNeoConversationRowController.h b/Telegram/Watch/Extension/TGNeoConversationRowController.h similarity index 100% rename from Watch/Extension/TGNeoConversationRowController.h rename to Telegram/Watch/Extension/TGNeoConversationRowController.h diff --git a/Watch/Extension/TGNeoConversationRowController.m b/Telegram/Watch/Extension/TGNeoConversationRowController.m similarity index 100% rename from Watch/Extension/TGNeoConversationRowController.m rename to Telegram/Watch/Extension/TGNeoConversationRowController.m diff --git a/Watch/Extension/TGNeoConversationSimpleRowController.h b/Telegram/Watch/Extension/TGNeoConversationSimpleRowController.h similarity index 100% rename from Watch/Extension/TGNeoConversationSimpleRowController.h rename to Telegram/Watch/Extension/TGNeoConversationSimpleRowController.h diff --git a/Watch/Extension/TGNeoConversationSimpleRowController.m b/Telegram/Watch/Extension/TGNeoConversationSimpleRowController.m similarity index 100% rename from Watch/Extension/TGNeoConversationSimpleRowController.m rename to Telegram/Watch/Extension/TGNeoConversationSimpleRowController.m diff --git a/Watch/Extension/TGNeoConversationStaticRowController.h b/Telegram/Watch/Extension/TGNeoConversationStaticRowController.h similarity index 100% rename from Watch/Extension/TGNeoConversationStaticRowController.h rename to Telegram/Watch/Extension/TGNeoConversationStaticRowController.h diff --git a/Watch/Extension/TGNeoConversationStaticRowController.m b/Telegram/Watch/Extension/TGNeoConversationStaticRowController.m similarity index 100% rename from Watch/Extension/TGNeoConversationStaticRowController.m rename to Telegram/Watch/Extension/TGNeoConversationStaticRowController.m diff --git a/Watch/Extension/TGNeoConversationTimeRowController.h b/Telegram/Watch/Extension/TGNeoConversationTimeRowController.h similarity index 100% rename from Watch/Extension/TGNeoConversationTimeRowController.h rename to Telegram/Watch/Extension/TGNeoConversationTimeRowController.h diff --git a/Watch/Extension/TGNeoConversationTimeRowController.m b/Telegram/Watch/Extension/TGNeoConversationTimeRowController.m similarity index 100% rename from Watch/Extension/TGNeoConversationTimeRowController.m rename to Telegram/Watch/Extension/TGNeoConversationTimeRowController.m diff --git a/Watch/Extension/TGNeoFileMessageViewModel.h b/Telegram/Watch/Extension/TGNeoFileMessageViewModel.h similarity index 100% rename from Watch/Extension/TGNeoFileMessageViewModel.h rename to Telegram/Watch/Extension/TGNeoFileMessageViewModel.h diff --git a/Watch/Extension/TGNeoFileMessageViewModel.m b/Telegram/Watch/Extension/TGNeoFileMessageViewModel.m similarity index 100% rename from Watch/Extension/TGNeoFileMessageViewModel.m rename to Telegram/Watch/Extension/TGNeoFileMessageViewModel.m diff --git a/Watch/Extension/TGNeoForwardHeaderViewModel.h b/Telegram/Watch/Extension/TGNeoForwardHeaderViewModel.h similarity index 100% rename from Watch/Extension/TGNeoForwardHeaderViewModel.h rename to Telegram/Watch/Extension/TGNeoForwardHeaderViewModel.h diff --git a/Watch/Extension/TGNeoForwardHeaderViewModel.m b/Telegram/Watch/Extension/TGNeoForwardHeaderViewModel.m similarity index 100% rename from Watch/Extension/TGNeoForwardHeaderViewModel.m rename to Telegram/Watch/Extension/TGNeoForwardHeaderViewModel.m diff --git a/Watch/Extension/TGNeoImageViewModel.h b/Telegram/Watch/Extension/TGNeoImageViewModel.h similarity index 100% rename from Watch/Extension/TGNeoImageViewModel.h rename to Telegram/Watch/Extension/TGNeoImageViewModel.h diff --git a/Watch/Extension/TGNeoImageViewModel.m b/Telegram/Watch/Extension/TGNeoImageViewModel.m similarity index 100% rename from Watch/Extension/TGNeoImageViewModel.m rename to Telegram/Watch/Extension/TGNeoImageViewModel.m diff --git a/Watch/Extension/TGNeoLabelViewModel.h b/Telegram/Watch/Extension/TGNeoLabelViewModel.h similarity index 100% rename from Watch/Extension/TGNeoLabelViewModel.h rename to Telegram/Watch/Extension/TGNeoLabelViewModel.h diff --git a/Watch/Extension/TGNeoLabelViewModel.m b/Telegram/Watch/Extension/TGNeoLabelViewModel.m similarity index 100% rename from Watch/Extension/TGNeoLabelViewModel.m rename to Telegram/Watch/Extension/TGNeoLabelViewModel.m diff --git a/Watch/Extension/TGNeoMediaMessageViewModel.h b/Telegram/Watch/Extension/TGNeoMediaMessageViewModel.h similarity index 100% rename from Watch/Extension/TGNeoMediaMessageViewModel.h rename to Telegram/Watch/Extension/TGNeoMediaMessageViewModel.h diff --git a/Watch/Extension/TGNeoMediaMessageViewModel.m b/Telegram/Watch/Extension/TGNeoMediaMessageViewModel.m similarity index 100% rename from Watch/Extension/TGNeoMediaMessageViewModel.m rename to Telegram/Watch/Extension/TGNeoMediaMessageViewModel.m diff --git a/Watch/Extension/TGNeoMessageViewModel.h b/Telegram/Watch/Extension/TGNeoMessageViewModel.h similarity index 100% rename from Watch/Extension/TGNeoMessageViewModel.h rename to Telegram/Watch/Extension/TGNeoMessageViewModel.h diff --git a/Watch/Extension/TGNeoMessageViewModel.m b/Telegram/Watch/Extension/TGNeoMessageViewModel.m similarity index 100% rename from Watch/Extension/TGNeoMessageViewModel.m rename to Telegram/Watch/Extension/TGNeoMessageViewModel.m diff --git a/Watch/Extension/TGNeoRenderableViewModel.h b/Telegram/Watch/Extension/TGNeoRenderableViewModel.h similarity index 100% rename from Watch/Extension/TGNeoRenderableViewModel.h rename to Telegram/Watch/Extension/TGNeoRenderableViewModel.h diff --git a/Watch/Extension/TGNeoRenderableViewModel.m b/Telegram/Watch/Extension/TGNeoRenderableViewModel.m similarity index 100% rename from Watch/Extension/TGNeoRenderableViewModel.m rename to Telegram/Watch/Extension/TGNeoRenderableViewModel.m diff --git a/Watch/Extension/TGNeoReplyHeaderViewModel.h b/Telegram/Watch/Extension/TGNeoReplyHeaderViewModel.h similarity index 100% rename from Watch/Extension/TGNeoReplyHeaderViewModel.h rename to Telegram/Watch/Extension/TGNeoReplyHeaderViewModel.h diff --git a/Watch/Extension/TGNeoReplyHeaderViewModel.m b/Telegram/Watch/Extension/TGNeoReplyHeaderViewModel.m similarity index 100% rename from Watch/Extension/TGNeoReplyHeaderViewModel.m rename to Telegram/Watch/Extension/TGNeoReplyHeaderViewModel.m diff --git a/Watch/Extension/TGNeoRowController.h b/Telegram/Watch/Extension/TGNeoRowController.h similarity index 100% rename from Watch/Extension/TGNeoRowController.h rename to Telegram/Watch/Extension/TGNeoRowController.h diff --git a/Watch/Extension/TGNeoRowController.m b/Telegram/Watch/Extension/TGNeoRowController.m similarity index 100% rename from Watch/Extension/TGNeoRowController.m rename to Telegram/Watch/Extension/TGNeoRowController.m diff --git a/Watch/Extension/TGNeoServiceMessageViewModel.h b/Telegram/Watch/Extension/TGNeoServiceMessageViewModel.h similarity index 100% rename from Watch/Extension/TGNeoServiceMessageViewModel.h rename to Telegram/Watch/Extension/TGNeoServiceMessageViewModel.h diff --git a/Watch/Extension/TGNeoServiceMessageViewModel.m b/Telegram/Watch/Extension/TGNeoServiceMessageViewModel.m similarity index 100% rename from Watch/Extension/TGNeoServiceMessageViewModel.m rename to Telegram/Watch/Extension/TGNeoServiceMessageViewModel.m diff --git a/Watch/Extension/TGNeoSmiliesMessageViewModel.h b/Telegram/Watch/Extension/TGNeoSmiliesMessageViewModel.h similarity index 100% rename from Watch/Extension/TGNeoSmiliesMessageViewModel.h rename to Telegram/Watch/Extension/TGNeoSmiliesMessageViewModel.h diff --git a/Watch/Extension/TGNeoSmiliesMessageViewModel.m b/Telegram/Watch/Extension/TGNeoSmiliesMessageViewModel.m similarity index 100% rename from Watch/Extension/TGNeoSmiliesMessageViewModel.m rename to Telegram/Watch/Extension/TGNeoSmiliesMessageViewModel.m diff --git a/Watch/Extension/TGNeoStickerMessageViewModel.h b/Telegram/Watch/Extension/TGNeoStickerMessageViewModel.h similarity index 100% rename from Watch/Extension/TGNeoStickerMessageViewModel.h rename to Telegram/Watch/Extension/TGNeoStickerMessageViewModel.h diff --git a/Watch/Extension/TGNeoStickerMessageViewModel.m b/Telegram/Watch/Extension/TGNeoStickerMessageViewModel.m similarity index 100% rename from Watch/Extension/TGNeoStickerMessageViewModel.m rename to Telegram/Watch/Extension/TGNeoStickerMessageViewModel.m diff --git a/Watch/Extension/TGNeoTextMessageViewModel.h b/Telegram/Watch/Extension/TGNeoTextMessageViewModel.h similarity index 100% rename from Watch/Extension/TGNeoTextMessageViewModel.h rename to Telegram/Watch/Extension/TGNeoTextMessageViewModel.h diff --git a/Watch/Extension/TGNeoTextMessageViewModel.m b/Telegram/Watch/Extension/TGNeoTextMessageViewModel.m similarity index 100% rename from Watch/Extension/TGNeoTextMessageViewModel.m rename to Telegram/Watch/Extension/TGNeoTextMessageViewModel.m diff --git a/Watch/Extension/TGNeoUnsupportedMessageViewModel.h b/Telegram/Watch/Extension/TGNeoUnsupportedMessageViewModel.h similarity index 100% rename from Watch/Extension/TGNeoUnsupportedMessageViewModel.h rename to Telegram/Watch/Extension/TGNeoUnsupportedMessageViewModel.h diff --git a/Watch/Extension/TGNeoUnsupportedMessageViewModel.m b/Telegram/Watch/Extension/TGNeoUnsupportedMessageViewModel.m similarity index 100% rename from Watch/Extension/TGNeoUnsupportedMessageViewModel.m rename to Telegram/Watch/Extension/TGNeoUnsupportedMessageViewModel.m diff --git a/Watch/Extension/TGNeoVenueMessageViewModel.h b/Telegram/Watch/Extension/TGNeoVenueMessageViewModel.h similarity index 100% rename from Watch/Extension/TGNeoVenueMessageViewModel.h rename to Telegram/Watch/Extension/TGNeoVenueMessageViewModel.h diff --git a/Watch/Extension/TGNeoVenueMessageViewModel.m b/Telegram/Watch/Extension/TGNeoVenueMessageViewModel.m similarity index 100% rename from Watch/Extension/TGNeoVenueMessageViewModel.m rename to Telegram/Watch/Extension/TGNeoVenueMessageViewModel.m diff --git a/Watch/Extension/TGNeoViewModel.h b/Telegram/Watch/Extension/TGNeoViewModel.h similarity index 100% rename from Watch/Extension/TGNeoViewModel.h rename to Telegram/Watch/Extension/TGNeoViewModel.h diff --git a/Watch/Extension/TGNeoViewModel.m b/Telegram/Watch/Extension/TGNeoViewModel.m similarity index 100% rename from Watch/Extension/TGNeoViewModel.m rename to Telegram/Watch/Extension/TGNeoViewModel.m diff --git a/Watch/Extension/TGNotificationController.h b/Telegram/Watch/Extension/TGNotificationController.h similarity index 100% rename from Watch/Extension/TGNotificationController.h rename to Telegram/Watch/Extension/TGNotificationController.h diff --git a/Watch/Extension/TGNotificationController.m b/Telegram/Watch/Extension/TGNotificationController.m similarity index 100% rename from Watch/Extension/TGNotificationController.m rename to Telegram/Watch/Extension/TGNotificationController.m diff --git a/Watch/Extension/TGProfilePhotoController.h b/Telegram/Watch/Extension/TGProfilePhotoController.h similarity index 100% rename from Watch/Extension/TGProfilePhotoController.h rename to Telegram/Watch/Extension/TGProfilePhotoController.h diff --git a/Watch/Extension/TGProfilePhotoController.m b/Telegram/Watch/Extension/TGProfilePhotoController.m similarity index 100% rename from Watch/Extension/TGProfilePhotoController.m rename to Telegram/Watch/Extension/TGProfilePhotoController.m diff --git a/Watch/Extension/TGStickerPackRowController.h b/Telegram/Watch/Extension/TGStickerPackRowController.h similarity index 100% rename from Watch/Extension/TGStickerPackRowController.h rename to Telegram/Watch/Extension/TGStickerPackRowController.h diff --git a/Watch/Extension/TGStickerPackRowController.m b/Telegram/Watch/Extension/TGStickerPackRowController.m similarity index 100% rename from Watch/Extension/TGStickerPackRowController.m rename to Telegram/Watch/Extension/TGStickerPackRowController.m diff --git a/Watch/Extension/TGStickerPacksController.h b/Telegram/Watch/Extension/TGStickerPacksController.h similarity index 100% rename from Watch/Extension/TGStickerPacksController.h rename to Telegram/Watch/Extension/TGStickerPacksController.h diff --git a/Watch/Extension/TGStickerPacksController.m b/Telegram/Watch/Extension/TGStickerPacksController.m similarity index 100% rename from Watch/Extension/TGStickerPacksController.m rename to Telegram/Watch/Extension/TGStickerPacksController.m diff --git a/Watch/Extension/TGStickersController.h b/Telegram/Watch/Extension/TGStickersController.h similarity index 100% rename from Watch/Extension/TGStickersController.h rename to Telegram/Watch/Extension/TGStickersController.h diff --git a/Watch/Extension/TGStickersController.m b/Telegram/Watch/Extension/TGStickersController.m similarity index 100% rename from Watch/Extension/TGStickersController.m rename to Telegram/Watch/Extension/TGStickersController.m diff --git a/Watch/Extension/TGStickersHeaderController.h b/Telegram/Watch/Extension/TGStickersHeaderController.h similarity index 100% rename from Watch/Extension/TGStickersHeaderController.h rename to Telegram/Watch/Extension/TGStickersHeaderController.h diff --git a/Watch/Extension/TGStickersHeaderController.m b/Telegram/Watch/Extension/TGStickersHeaderController.m similarity index 100% rename from Watch/Extension/TGStickersHeaderController.m rename to Telegram/Watch/Extension/TGStickersHeaderController.m diff --git a/Watch/Extension/TGStickersRowController.h b/Telegram/Watch/Extension/TGStickersRowController.h similarity index 100% rename from Watch/Extension/TGStickersRowController.h rename to Telegram/Watch/Extension/TGStickersRowController.h diff --git a/Watch/Extension/TGStickersRowController.m b/Telegram/Watch/Extension/TGStickersRowController.m similarity index 100% rename from Watch/Extension/TGStickersRowController.m rename to Telegram/Watch/Extension/TGStickersRowController.m diff --git a/Watch/Extension/TGStickersSectionHeaderController.h b/Telegram/Watch/Extension/TGStickersSectionHeaderController.h similarity index 100% rename from Watch/Extension/TGStickersSectionHeaderController.h rename to Telegram/Watch/Extension/TGStickersSectionHeaderController.h diff --git a/Watch/Extension/TGStickersSectionHeaderController.m b/Telegram/Watch/Extension/TGStickersSectionHeaderController.m similarity index 100% rename from Watch/Extension/TGStickersSectionHeaderController.m rename to Telegram/Watch/Extension/TGStickersSectionHeaderController.m diff --git a/Watch/Extension/TGStringUtils.h b/Telegram/Watch/Extension/TGStringUtils.h similarity index 100% rename from Watch/Extension/TGStringUtils.h rename to Telegram/Watch/Extension/TGStringUtils.h diff --git a/Watch/Extension/TGStringUtils.m b/Telegram/Watch/Extension/TGStringUtils.m similarity index 100% rename from Watch/Extension/TGStringUtils.m rename to Telegram/Watch/Extension/TGStringUtils.m diff --git a/Watch/Extension/TGTableDeltaUpdater.h b/Telegram/Watch/Extension/TGTableDeltaUpdater.h similarity index 100% rename from Watch/Extension/TGTableDeltaUpdater.h rename to Telegram/Watch/Extension/TGTableDeltaUpdater.h diff --git a/Watch/Extension/TGTableDeltaUpdater.m b/Telegram/Watch/Extension/TGTableDeltaUpdater.m similarity index 100% rename from Watch/Extension/TGTableDeltaUpdater.m rename to Telegram/Watch/Extension/TGTableDeltaUpdater.m diff --git a/Watch/Extension/TGUserHandle.h b/Telegram/Watch/Extension/TGUserHandle.h similarity index 100% rename from Watch/Extension/TGUserHandle.h rename to Telegram/Watch/Extension/TGUserHandle.h diff --git a/Watch/Extension/TGUserHandle.m b/Telegram/Watch/Extension/TGUserHandle.m similarity index 100% rename from Watch/Extension/TGUserHandle.m rename to Telegram/Watch/Extension/TGUserHandle.m diff --git a/Watch/Extension/TGUserHandleRowController.h b/Telegram/Watch/Extension/TGUserHandleRowController.h similarity index 100% rename from Watch/Extension/TGUserHandleRowController.h rename to Telegram/Watch/Extension/TGUserHandleRowController.h diff --git a/Watch/Extension/TGUserHandleRowController.m b/Telegram/Watch/Extension/TGUserHandleRowController.m similarity index 100% rename from Watch/Extension/TGUserHandleRowController.m rename to Telegram/Watch/Extension/TGUserHandleRowController.m diff --git a/Watch/Extension/TGUserInfoController.h b/Telegram/Watch/Extension/TGUserInfoController.h similarity index 100% rename from Watch/Extension/TGUserInfoController.h rename to Telegram/Watch/Extension/TGUserInfoController.h diff --git a/Watch/Extension/TGUserInfoController.m b/Telegram/Watch/Extension/TGUserInfoController.m similarity index 100% rename from Watch/Extension/TGUserInfoController.m rename to Telegram/Watch/Extension/TGUserInfoController.m diff --git a/Watch/Extension/TGUserInfoHeaderController.h b/Telegram/Watch/Extension/TGUserInfoHeaderController.h similarity index 100% rename from Watch/Extension/TGUserInfoHeaderController.h rename to Telegram/Watch/Extension/TGUserInfoHeaderController.h diff --git a/Watch/Extension/TGUserInfoHeaderController.m b/Telegram/Watch/Extension/TGUserInfoHeaderController.m similarity index 100% rename from Watch/Extension/TGUserInfoHeaderController.m rename to Telegram/Watch/Extension/TGUserInfoHeaderController.m diff --git a/Watch/Extension/TGUserRowController.h b/Telegram/Watch/Extension/TGUserRowController.h similarity index 100% rename from Watch/Extension/TGUserRowController.h rename to Telegram/Watch/Extension/TGUserRowController.h diff --git a/Watch/Extension/TGUserRowController.m b/Telegram/Watch/Extension/TGUserRowController.m similarity index 100% rename from Watch/Extension/TGUserRowController.m rename to Telegram/Watch/Extension/TGUserRowController.m diff --git a/Watch/Extension/TGWatchColor.h b/Telegram/Watch/Extension/TGWatchColor.h similarity index 100% rename from Watch/Extension/TGWatchColor.h rename to Telegram/Watch/Extension/TGWatchColor.h diff --git a/Watch/Extension/TGWatchColor.m b/Telegram/Watch/Extension/TGWatchColor.m similarity index 100% rename from Watch/Extension/TGWatchColor.m rename to Telegram/Watch/Extension/TGWatchColor.m diff --git a/Watch/Extension/TGWatchCommon.h b/Telegram/Watch/Extension/TGWatchCommon.h similarity index 100% rename from Watch/Extension/TGWatchCommon.h rename to Telegram/Watch/Extension/TGWatchCommon.h diff --git a/Watch/Extension/TGWatchCommon.m b/Telegram/Watch/Extension/TGWatchCommon.m similarity index 100% rename from Watch/Extension/TGWatchCommon.m rename to Telegram/Watch/Extension/TGWatchCommon.m diff --git a/Watch/Extension/WKInterface+TGInterface.h b/Telegram/Watch/Extension/WKInterface+TGInterface.h similarity index 100% rename from Watch/Extension/WKInterface+TGInterface.h rename to Telegram/Watch/Extension/WKInterface+TGInterface.h diff --git a/Watch/Extension/WKInterface+TGInterface.m b/Telegram/Watch/Extension/WKInterface+TGInterface.m similarity index 100% rename from Watch/Extension/WKInterface+TGInterface.m rename to Telegram/Watch/Extension/WKInterface+TGInterface.m diff --git a/Watch/Extension/WKInterfaceGroup+Signals.h b/Telegram/Watch/Extension/WKInterfaceGroup+Signals.h similarity index 100% rename from Watch/Extension/WKInterfaceGroup+Signals.h rename to Telegram/Watch/Extension/WKInterfaceGroup+Signals.h diff --git a/Watch/Extension/WKInterfaceGroup+Signals.m b/Telegram/Watch/Extension/WKInterfaceGroup+Signals.m similarity index 100% rename from Watch/Extension/WKInterfaceGroup+Signals.m rename to Telegram/Watch/Extension/WKInterfaceGroup+Signals.m diff --git a/Watch/Extension/WKInterfaceImage+Signals.h b/Telegram/Watch/Extension/WKInterfaceImage+Signals.h similarity index 100% rename from Watch/Extension/WKInterfaceImage+Signals.h rename to Telegram/Watch/Extension/WKInterfaceImage+Signals.h diff --git a/Watch/Extension/WKInterfaceImage+Signals.m b/Telegram/Watch/Extension/WKInterfaceImage+Signals.m similarity index 100% rename from Watch/Extension/WKInterfaceImage+Signals.m rename to Telegram/Watch/Extension/WKInterfaceImage+Signals.m diff --git a/Watch/Extension/WKInterfaceTable+TGDataDrivenTable.h b/Telegram/Watch/Extension/WKInterfaceTable+TGDataDrivenTable.h similarity index 100% rename from Watch/Extension/WKInterfaceTable+TGDataDrivenTable.h rename to Telegram/Watch/Extension/WKInterfaceTable+TGDataDrivenTable.h diff --git a/Watch/Extension/WKInterfaceTable+TGDataDrivenTable.m b/Telegram/Watch/Extension/WKInterfaceTable+TGDataDrivenTable.m similarity index 100% rename from Watch/Extension/WKInterfaceTable+TGDataDrivenTable.m rename to Telegram/Watch/Extension/WKInterfaceTable+TGDataDrivenTable.m diff --git a/Watch/Extension/WatchExtension-Prefix.pch b/Telegram/Watch/Extension/WatchExtension-Prefix.pch similarity index 100% rename from Watch/Extension/WatchExtension-Prefix.pch rename to Telegram/Watch/Extension/WatchExtension-Prefix.pch diff --git a/Watch/WatchCommonWatch/TGBridgeActionMediaAttachment.h b/Telegram/Watch/WatchCommonWatch/TGBridgeActionMediaAttachment.h similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeActionMediaAttachment.h rename to Telegram/Watch/WatchCommonWatch/TGBridgeActionMediaAttachment.h diff --git a/Watch/WatchCommonWatch/TGBridgeActionMediaAttachment.m b/Telegram/Watch/WatchCommonWatch/TGBridgeActionMediaAttachment.m similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeActionMediaAttachment.m rename to Telegram/Watch/WatchCommonWatch/TGBridgeActionMediaAttachment.m diff --git a/Watch/WatchCommonWatch/TGBridgeAudioMediaAttachment.h b/Telegram/Watch/WatchCommonWatch/TGBridgeAudioMediaAttachment.h similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeAudioMediaAttachment.h rename to Telegram/Watch/WatchCommonWatch/TGBridgeAudioMediaAttachment.h diff --git a/Watch/WatchCommonWatch/TGBridgeAudioMediaAttachment.m b/Telegram/Watch/WatchCommonWatch/TGBridgeAudioMediaAttachment.m similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeAudioMediaAttachment.m rename to Telegram/Watch/WatchCommonWatch/TGBridgeAudioMediaAttachment.m diff --git a/Watch/WatchCommonWatch/TGBridgeBotCommandInfo.h b/Telegram/Watch/WatchCommonWatch/TGBridgeBotCommandInfo.h similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeBotCommandInfo.h rename to Telegram/Watch/WatchCommonWatch/TGBridgeBotCommandInfo.h diff --git a/Watch/WatchCommonWatch/TGBridgeBotCommandInfo.m b/Telegram/Watch/WatchCommonWatch/TGBridgeBotCommandInfo.m similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeBotCommandInfo.m rename to Telegram/Watch/WatchCommonWatch/TGBridgeBotCommandInfo.m diff --git a/Watch/WatchCommonWatch/TGBridgeBotInfo.h b/Telegram/Watch/WatchCommonWatch/TGBridgeBotInfo.h similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeBotInfo.h rename to Telegram/Watch/WatchCommonWatch/TGBridgeBotInfo.h diff --git a/Watch/WatchCommonWatch/TGBridgeBotInfo.m b/Telegram/Watch/WatchCommonWatch/TGBridgeBotInfo.m similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeBotInfo.m rename to Telegram/Watch/WatchCommonWatch/TGBridgeBotInfo.m diff --git a/Watch/WatchCommonWatch/TGBridgeChat.h b/Telegram/Watch/WatchCommonWatch/TGBridgeChat.h similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeChat.h rename to Telegram/Watch/WatchCommonWatch/TGBridgeChat.h diff --git a/Watch/WatchCommonWatch/TGBridgeChat.m b/Telegram/Watch/WatchCommonWatch/TGBridgeChat.m similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeChat.m rename to Telegram/Watch/WatchCommonWatch/TGBridgeChat.m diff --git a/Watch/WatchCommonWatch/TGBridgeChatMessages.h b/Telegram/Watch/WatchCommonWatch/TGBridgeChatMessages.h similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeChatMessages.h rename to Telegram/Watch/WatchCommonWatch/TGBridgeChatMessages.h diff --git a/Watch/WatchCommonWatch/TGBridgeChatMessages.m b/Telegram/Watch/WatchCommonWatch/TGBridgeChatMessages.m similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeChatMessages.m rename to Telegram/Watch/WatchCommonWatch/TGBridgeChatMessages.m diff --git a/Watch/WatchCommonWatch/TGBridgeCommon.h b/Telegram/Watch/WatchCommonWatch/TGBridgeCommon.h similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeCommon.h rename to Telegram/Watch/WatchCommonWatch/TGBridgeCommon.h diff --git a/Watch/WatchCommonWatch/TGBridgeCommon.m b/Telegram/Watch/WatchCommonWatch/TGBridgeCommon.m similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeCommon.m rename to Telegram/Watch/WatchCommonWatch/TGBridgeCommon.m diff --git a/Watch/WatchCommonWatch/TGBridgeContactMediaAttachment.h b/Telegram/Watch/WatchCommonWatch/TGBridgeContactMediaAttachment.h similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeContactMediaAttachment.h rename to Telegram/Watch/WatchCommonWatch/TGBridgeContactMediaAttachment.h diff --git a/Watch/WatchCommonWatch/TGBridgeContactMediaAttachment.m b/Telegram/Watch/WatchCommonWatch/TGBridgeContactMediaAttachment.m similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeContactMediaAttachment.m rename to Telegram/Watch/WatchCommonWatch/TGBridgeContactMediaAttachment.m diff --git a/Watch/WatchCommonWatch/TGBridgeContext.h b/Telegram/Watch/WatchCommonWatch/TGBridgeContext.h similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeContext.h rename to Telegram/Watch/WatchCommonWatch/TGBridgeContext.h diff --git a/Watch/WatchCommonWatch/TGBridgeContext.m b/Telegram/Watch/WatchCommonWatch/TGBridgeContext.m similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeContext.m rename to Telegram/Watch/WatchCommonWatch/TGBridgeContext.m diff --git a/Watch/WatchCommonWatch/TGBridgeDocumentMediaAttachment.h b/Telegram/Watch/WatchCommonWatch/TGBridgeDocumentMediaAttachment.h similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeDocumentMediaAttachment.h rename to Telegram/Watch/WatchCommonWatch/TGBridgeDocumentMediaAttachment.h diff --git a/Watch/WatchCommonWatch/TGBridgeDocumentMediaAttachment.m b/Telegram/Watch/WatchCommonWatch/TGBridgeDocumentMediaAttachment.m similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeDocumentMediaAttachment.m rename to Telegram/Watch/WatchCommonWatch/TGBridgeDocumentMediaAttachment.m diff --git a/Watch/WatchCommonWatch/TGBridgeForwardedMessageMediaAttachment.h b/Telegram/Watch/WatchCommonWatch/TGBridgeForwardedMessageMediaAttachment.h similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeForwardedMessageMediaAttachment.h rename to Telegram/Watch/WatchCommonWatch/TGBridgeForwardedMessageMediaAttachment.h diff --git a/Watch/WatchCommonWatch/TGBridgeForwardedMessageMediaAttachment.m b/Telegram/Watch/WatchCommonWatch/TGBridgeForwardedMessageMediaAttachment.m similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeForwardedMessageMediaAttachment.m rename to Telegram/Watch/WatchCommonWatch/TGBridgeForwardedMessageMediaAttachment.m diff --git a/Watch/WatchCommonWatch/TGBridgeImageMediaAttachment.h b/Telegram/Watch/WatchCommonWatch/TGBridgeImageMediaAttachment.h similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeImageMediaAttachment.h rename to Telegram/Watch/WatchCommonWatch/TGBridgeImageMediaAttachment.h diff --git a/Watch/WatchCommonWatch/TGBridgeImageMediaAttachment.m b/Telegram/Watch/WatchCommonWatch/TGBridgeImageMediaAttachment.m similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeImageMediaAttachment.m rename to Telegram/Watch/WatchCommonWatch/TGBridgeImageMediaAttachment.m diff --git a/Watch/WatchCommonWatch/TGBridgeLocationMediaAttachment.h b/Telegram/Watch/WatchCommonWatch/TGBridgeLocationMediaAttachment.h similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeLocationMediaAttachment.h rename to Telegram/Watch/WatchCommonWatch/TGBridgeLocationMediaAttachment.h diff --git a/Watch/WatchCommonWatch/TGBridgeLocationMediaAttachment.m b/Telegram/Watch/WatchCommonWatch/TGBridgeLocationMediaAttachment.m similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeLocationMediaAttachment.m rename to Telegram/Watch/WatchCommonWatch/TGBridgeLocationMediaAttachment.m diff --git a/Watch/WatchCommonWatch/TGBridgeLocationVenue.h b/Telegram/Watch/WatchCommonWatch/TGBridgeLocationVenue.h similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeLocationVenue.h rename to Telegram/Watch/WatchCommonWatch/TGBridgeLocationVenue.h diff --git a/Watch/WatchCommonWatch/TGBridgeLocationVenue.m b/Telegram/Watch/WatchCommonWatch/TGBridgeLocationVenue.m similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeLocationVenue.m rename to Telegram/Watch/WatchCommonWatch/TGBridgeLocationVenue.m diff --git a/Watch/WatchCommonWatch/TGBridgeMediaAttachment.h b/Telegram/Watch/WatchCommonWatch/TGBridgeMediaAttachment.h similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeMediaAttachment.h rename to Telegram/Watch/WatchCommonWatch/TGBridgeMediaAttachment.h diff --git a/Watch/WatchCommonWatch/TGBridgeMediaAttachment.m b/Telegram/Watch/WatchCommonWatch/TGBridgeMediaAttachment.m similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeMediaAttachment.m rename to Telegram/Watch/WatchCommonWatch/TGBridgeMediaAttachment.m diff --git a/Watch/WatchCommonWatch/TGBridgeMessage.h b/Telegram/Watch/WatchCommonWatch/TGBridgeMessage.h similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeMessage.h rename to Telegram/Watch/WatchCommonWatch/TGBridgeMessage.h diff --git a/Watch/WatchCommonWatch/TGBridgeMessage.m b/Telegram/Watch/WatchCommonWatch/TGBridgeMessage.m similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeMessage.m rename to Telegram/Watch/WatchCommonWatch/TGBridgeMessage.m diff --git a/Watch/WatchCommonWatch/TGBridgeMessageEntities.h b/Telegram/Watch/WatchCommonWatch/TGBridgeMessageEntities.h similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeMessageEntities.h rename to Telegram/Watch/WatchCommonWatch/TGBridgeMessageEntities.h diff --git a/Watch/WatchCommonWatch/TGBridgeMessageEntities.m b/Telegram/Watch/WatchCommonWatch/TGBridgeMessageEntities.m similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeMessageEntities.m rename to Telegram/Watch/WatchCommonWatch/TGBridgeMessageEntities.m diff --git a/Watch/WatchCommonWatch/TGBridgeMessageEntitiesAttachment.h b/Telegram/Watch/WatchCommonWatch/TGBridgeMessageEntitiesAttachment.h similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeMessageEntitiesAttachment.h rename to Telegram/Watch/WatchCommonWatch/TGBridgeMessageEntitiesAttachment.h diff --git a/Watch/WatchCommonWatch/TGBridgeMessageEntitiesAttachment.m b/Telegram/Watch/WatchCommonWatch/TGBridgeMessageEntitiesAttachment.m similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeMessageEntitiesAttachment.m rename to Telegram/Watch/WatchCommonWatch/TGBridgeMessageEntitiesAttachment.m diff --git a/Watch/WatchCommonWatch/TGBridgePeerIdAdapter.h b/Telegram/Watch/WatchCommonWatch/TGBridgePeerIdAdapter.h similarity index 100% rename from Watch/WatchCommonWatch/TGBridgePeerIdAdapter.h rename to Telegram/Watch/WatchCommonWatch/TGBridgePeerIdAdapter.h diff --git a/Watch/WatchCommonWatch/TGBridgePeerNotificationSettings.h b/Telegram/Watch/WatchCommonWatch/TGBridgePeerNotificationSettings.h similarity index 100% rename from Watch/WatchCommonWatch/TGBridgePeerNotificationSettings.h rename to Telegram/Watch/WatchCommonWatch/TGBridgePeerNotificationSettings.h diff --git a/Watch/WatchCommonWatch/TGBridgePeerNotificationSettings.m b/Telegram/Watch/WatchCommonWatch/TGBridgePeerNotificationSettings.m similarity index 100% rename from Watch/WatchCommonWatch/TGBridgePeerNotificationSettings.m rename to Telegram/Watch/WatchCommonWatch/TGBridgePeerNotificationSettings.m diff --git a/Watch/WatchCommonWatch/TGBridgeReplyMarkupMediaAttachment.h b/Telegram/Watch/WatchCommonWatch/TGBridgeReplyMarkupMediaAttachment.h similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeReplyMarkupMediaAttachment.h rename to Telegram/Watch/WatchCommonWatch/TGBridgeReplyMarkupMediaAttachment.h diff --git a/Watch/WatchCommonWatch/TGBridgeReplyMarkupMediaAttachment.m b/Telegram/Watch/WatchCommonWatch/TGBridgeReplyMarkupMediaAttachment.m similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeReplyMarkupMediaAttachment.m rename to Telegram/Watch/WatchCommonWatch/TGBridgeReplyMarkupMediaAttachment.m diff --git a/Watch/WatchCommonWatch/TGBridgeReplyMessageMediaAttachment.h b/Telegram/Watch/WatchCommonWatch/TGBridgeReplyMessageMediaAttachment.h similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeReplyMessageMediaAttachment.h rename to Telegram/Watch/WatchCommonWatch/TGBridgeReplyMessageMediaAttachment.h diff --git a/Watch/WatchCommonWatch/TGBridgeReplyMessageMediaAttachment.m b/Telegram/Watch/WatchCommonWatch/TGBridgeReplyMessageMediaAttachment.m similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeReplyMessageMediaAttachment.m rename to Telegram/Watch/WatchCommonWatch/TGBridgeReplyMessageMediaAttachment.m diff --git a/Watch/WatchCommonWatch/TGBridgeSubscriptions.h b/Telegram/Watch/WatchCommonWatch/TGBridgeSubscriptions.h similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeSubscriptions.h rename to Telegram/Watch/WatchCommonWatch/TGBridgeSubscriptions.h diff --git a/Watch/WatchCommonWatch/TGBridgeSubscriptions.m b/Telegram/Watch/WatchCommonWatch/TGBridgeSubscriptions.m similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeSubscriptions.m rename to Telegram/Watch/WatchCommonWatch/TGBridgeSubscriptions.m diff --git a/Watch/WatchCommonWatch/TGBridgeUnsupportedMediaAttachment.h b/Telegram/Watch/WatchCommonWatch/TGBridgeUnsupportedMediaAttachment.h similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeUnsupportedMediaAttachment.h rename to Telegram/Watch/WatchCommonWatch/TGBridgeUnsupportedMediaAttachment.h diff --git a/Watch/WatchCommonWatch/TGBridgeUnsupportedMediaAttachment.m b/Telegram/Watch/WatchCommonWatch/TGBridgeUnsupportedMediaAttachment.m similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeUnsupportedMediaAttachment.m rename to Telegram/Watch/WatchCommonWatch/TGBridgeUnsupportedMediaAttachment.m diff --git a/Watch/WatchCommonWatch/TGBridgeUser.h b/Telegram/Watch/WatchCommonWatch/TGBridgeUser.h similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeUser.h rename to Telegram/Watch/WatchCommonWatch/TGBridgeUser.h diff --git a/Watch/WatchCommonWatch/TGBridgeUser.m b/Telegram/Watch/WatchCommonWatch/TGBridgeUser.m similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeUser.m rename to Telegram/Watch/WatchCommonWatch/TGBridgeUser.m diff --git a/Watch/WatchCommonWatch/TGBridgeVideoMediaAttachment.h b/Telegram/Watch/WatchCommonWatch/TGBridgeVideoMediaAttachment.h similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeVideoMediaAttachment.h rename to Telegram/Watch/WatchCommonWatch/TGBridgeVideoMediaAttachment.h diff --git a/Watch/WatchCommonWatch/TGBridgeVideoMediaAttachment.m b/Telegram/Watch/WatchCommonWatch/TGBridgeVideoMediaAttachment.m similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeVideoMediaAttachment.m rename to Telegram/Watch/WatchCommonWatch/TGBridgeVideoMediaAttachment.m diff --git a/Watch/WatchCommonWatch/TGBridgeWebPageMediaAttachment.h b/Telegram/Watch/WatchCommonWatch/TGBridgeWebPageMediaAttachment.h similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeWebPageMediaAttachment.h rename to Telegram/Watch/WatchCommonWatch/TGBridgeWebPageMediaAttachment.h diff --git a/Watch/WatchCommonWatch/TGBridgeWebPageMediaAttachment.m b/Telegram/Watch/WatchCommonWatch/TGBridgeWebPageMediaAttachment.m similarity index 100% rename from Watch/WatchCommonWatch/TGBridgeWebPageMediaAttachment.m rename to Telegram/Watch/WatchCommonWatch/TGBridgeWebPageMediaAttachment.m diff --git a/Watch/WatchCommonWatch/WatchCommonWatch.h b/Telegram/Watch/WatchCommonWatch/WatchCommonWatch.h similarity index 100% rename from Watch/WatchCommonWatch/WatchCommonWatch.h rename to Telegram/Watch/WatchCommonWatch/WatchCommonWatch.h diff --git a/Widget/Info.plist b/Telegram/Widget/Info.plist similarity index 100% rename from Widget/Info.plist rename to Telegram/Widget/Info.plist diff --git a/Widget/PeerNode.swift b/Telegram/Widget/PeerNode.swift similarity index 100% rename from Widget/PeerNode.swift rename to Telegram/Widget/PeerNode.swift diff --git a/Widget/TodayViewController.swift b/Telegram/Widget/TodayViewController.swift similarity index 100% rename from Widget/TodayViewController.swift rename to Telegram/Widget/TodayViewController.swift diff --git a/Widget/Widget-Bridging-Header.h b/Telegram/Widget/Widget-Bridging-Header.h similarity index 100% rename from Widget/Widget-Bridging-Header.h rename to Telegram/Widget/Widget-Bridging-Header.h diff --git a/Widget/ar.lproj/InfoPlist.strings b/Telegram/Widget/ar.lproj/InfoPlist.strings similarity index 100% rename from Widget/ar.lproj/InfoPlist.strings rename to Telegram/Widget/ar.lproj/InfoPlist.strings diff --git a/Widget/de.lproj/InfoPlist.strings b/Telegram/Widget/de.lproj/InfoPlist.strings similarity index 100% rename from Widget/de.lproj/InfoPlist.strings rename to Telegram/Widget/de.lproj/InfoPlist.strings diff --git a/Widget/en.lproj/InfoPlist.strings b/Telegram/Widget/en.lproj/InfoPlist.strings similarity index 100% rename from Widget/en.lproj/InfoPlist.strings rename to Telegram/Widget/en.lproj/InfoPlist.strings diff --git a/Widget/en.lproj/Localizable.strings b/Telegram/Widget/en.lproj/Localizable.strings similarity index 100% rename from Widget/en.lproj/Localizable.strings rename to Telegram/Widget/en.lproj/Localizable.strings diff --git a/Widget/es.lproj/InfoPlist.strings b/Telegram/Widget/es.lproj/InfoPlist.strings similarity index 100% rename from Widget/es.lproj/InfoPlist.strings rename to Telegram/Widget/es.lproj/InfoPlist.strings diff --git a/Widget/it.lproj/InfoPlist.strings b/Telegram/Widget/it.lproj/InfoPlist.strings similarity index 100% rename from Widget/it.lproj/InfoPlist.strings rename to Telegram/Widget/it.lproj/InfoPlist.strings diff --git a/Widget/ko.lproj/InfoPlist.strings b/Telegram/Widget/ko.lproj/InfoPlist.strings similarity index 100% rename from Widget/ko.lproj/InfoPlist.strings rename to Telegram/Widget/ko.lproj/InfoPlist.strings diff --git a/Widget/nl.lproj/InfoPlist.strings b/Telegram/Widget/nl.lproj/InfoPlist.strings similarity index 100% rename from Widget/nl.lproj/InfoPlist.strings rename to Telegram/Widget/nl.lproj/InfoPlist.strings diff --git a/Widget/pt.lproj/InfoPlist.strings b/Telegram/Widget/pt.lproj/InfoPlist.strings similarity index 100% rename from Widget/pt.lproj/InfoPlist.strings rename to Telegram/Widget/pt.lproj/InfoPlist.strings diff --git a/Widget/ru.lproj/InfoPlist.strings b/Telegram/Widget/ru.lproj/InfoPlist.strings similarity index 100% rename from Widget/ru.lproj/InfoPlist.strings rename to Telegram/Widget/ru.lproj/InfoPlist.strings diff --git a/Telegram/telegram_info_plist.bzl b/Telegram/telegram_info_plist.bzl new file mode 100644 index 0000000000..6e38968e65 --- /dev/null +++ b/Telegram/telegram_info_plist.bzl @@ -0,0 +1,77 @@ +load("//build-system:defines.bzl", + "string_value", +) + +def _telegram_info_plist(ctx): + output = ctx.outputs.out + + plist_string = """ + + + + + CFBundleShortVersionString + {app_version} + CFBundleVersion + {build_number} + CFBundleURLTypes + + + CFBundleTypeRole + Viewer + CFBundleURLName + {bundle_id} + CFBundleURLSchemes + + telegram + + + + CFBundleTypeRole + Viewer + CFBundleURLName + {bundle_id}.ton + CFBundleURLSchemes + + ton + + + + CFBundleTypeRole + Viewer + CFBundleURLName + {app_name}.compatibility + CFBundleURLSchemes + + {url_scheme} + + + + + + """.format( + app_version = string_value(ctx, ctx.attr.app_version_define), + build_number = string_value(ctx, ctx.attr.build_number_define), + bundle_id = string_value(ctx, ctx.attr.bundle_id_define), + app_name = ctx.attr.app_name, + url_scheme = ctx.attr.url_scheme, + ) + + ctx.actions.write( + output = output, + content = plist_string, + ) + +telegram_info_plist = rule( + implementation = _telegram_info_plist, + attrs = { + "app_name": attr.string(mandatory = True), + "url_scheme": attr.string(mandatory = True), + "bundle_id_define": attr.string(mandatory = True), + "app_version_define": attr.string(mandatory = True), + "build_number_define": attr.string(mandatory = True), + }, + outputs = { + "out": "%{name}.plist" + }, +) diff --git a/WORKSPACE b/WORKSPACE new file mode 100644 index 0000000000..069a35c57e --- /dev/null +++ b/WORKSPACE @@ -0,0 +1,52 @@ +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "com_google_protobuf", + urls = ["https://github.com/protocolbuffers/protobuf/archive/v3.8.0.zip"], + sha256 = "1e622ce4b84b88b6d2cdf1db38d1a634fe2392d74f0b7b74ff98f3a51838ee53", + strip_prefix = "protobuf-3.8.0", + type = "zip", +) + +load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps") +protobuf_deps() + +local_repository( + name = "build_bazel_rules_apple", + path = "build-system/bazel-rules/rules_apple", +) + +local_repository( + name = "build_bazel_rules_swift", + path = "build-system/bazel-rules/rules_swift", +) + +local_repository( + name = "build_bazel_apple_support", + path = "build-system/bazel-rules/apple_support", +) + +load( + "@build_bazel_rules_apple//apple:repositories.bzl", + "apple_rules_dependencies", +) + +apple_rules_dependencies() + +load( + "@build_bazel_rules_swift//swift:repositories.bzl", + "swift_rules_dependencies", +) + +swift_rules_dependencies() + +load( + "@build_bazel_apple_support//lib:repositories.bzl", + "apple_support_dependencies", +) + +apple_support_dependencies() + +load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") + +bazel_skylib_workspace() diff --git a/Wallet.makefile b/Wallet.makefile index 20ead97244..90d359969c 100644 --- a/Wallet.makefile +++ b/Wallet.makefile @@ -16,7 +16,10 @@ WALLET_BUCK_OPTIONS=\ --config custom.isInternalBuild="${IS_INTERNAL_BUILD}" \ --config custom.isAppStoreBuild="${IS_APPSTORE_BUILD}" \ --config custom.appStoreId="${APPSTORE_ID}" \ - --config custom.appSpecificUrlScheme="${APP_SPECIFIC_URL_SCHEME}" + --config custom.appSpecificUrlScheme="${APP_SPECIFIC_URL_SCHEME}" \ + --config buildfile.name=BUCK + +BAZEL=$(shell which bazel) wallet_deps: check_env $(BUCK) query "deps(//Wallet:AppPackage)" --output-attribute buck.type \ @@ -48,3 +51,5 @@ wallet_package: wallet_app: build_wallet wallet_package +tulsi_project: + ${HOME}/Applications/Tulsi.app/Contents/MacOS/Tulsi -- --genconfig Wallet/Wallet.tulsiproj:Default --bazel "${BAZEL}" diff --git a/Wallet/BUILD b/Wallet/BUILD new file mode 100644 index 0000000000..a924e4a93e --- /dev/null +++ b/Wallet/BUILD @@ -0,0 +1,111 @@ +load("@build_bazel_rules_apple//apple:ios.bzl", "ios_application", "ios_framework") +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +version_info_plist_source = """ +echo \ +'' \ +'' \ +'' \ +'' \ +' CFBundleShortVersionString' \ +' {}' \ +' CFBundleVersion' \ +' {}' \ +'' \ +'' \ +> "$@" +""".format("1.0", "30") + +genrule( + name = "VersionInfoPlist", + outs = ["VersionInfo.plist"], + cmd = version_info_plist_source, +) + +filegroup( + name = "Strings", + srcs = glob([ + "Strings/**/*", + ], exclude = ["Strings/**/.*"]), +) + +objc_library( + name = "Main", + srcs = [ + "Sources/main.m" + ], +) + +ios_framework( + name = "AsyncDisplayKitFramework", + deps = ["//submodules/AsyncDisplayKit:AsyncDisplayKit"], + bundle_id = "org.telegram.Telegram.AsyncDisplayKit", + families = ["iphone", "ipad"], + minimum_os_version = "9.0", + infoplists = [ + "Info.plist" + ], +) + +swift_library( + name = "Lib", + srcs = glob([ + "Sources/**/*.swift", + ]), + data = [ + ":Strings", + ], + deps = [ + "//submodules/GZip:GZip", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/SSignalKit/SSignalKit:SSignalKit", + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/ObjCRuntimeUtils:ObjCRuntimeUtils", + "//submodules/UIKitRuntimeUtils:UIKitRuntimeUtils", + "//submodules/Display:Display", + "//submodules/AlertUI:AlertUI", + "//submodules/ActivityIndicator:ActivityIndicator", + "//submodules/OverlayStatusController:OverlayStatusController", + "//submodules/openssl:openssl", + "//submodules/OpenSSLEncryptionProvider:OpenSSLEncryptionProvider", + "//submodules/WalletCore:WalletCore", + "//submodules/BuildConfig:BuildConfig", + "//submodules/AppBundle:AppBundle", + "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", + "//submodules/Camera:Camera", + "//submodules/QrCode:QrCode", + "//submodules/MergeLists:MergeLists", + "//submodules/GlassButtonNode:GlassButtonNode", + "//submodules/UrlEscaping:UrlEscaping", + "//submodules/LocalAuth:LocalAuth", + "//submodules/ScreenCaptureDetection:ScreenCaptureDetection", + "//submodules/WalletUrl:WalletUrl", + "//submodules/ProgressNavigationButtonNode:ProgressNavigationButtonNode", + "//submodules/Markdown:Markdown", + "//submodules/StringPluralization:StringPluralization", + "//submodules/YuvConversion:YuvConversion", + "//submodules/rlottie:RLottieBinding", + "//submodules/AnimatedStickerNode:AnimatedStickerNode", + "//submodules/WalletUI:WalletUI", + "//submodules/FFMpegBinding:FFMpegBinding", + ], +) + +ios_application( + name = "Wallet", + bundle_id = "{wallet_bundle_id}", + families = ["iphone", "ipad"], + minimum_os_version = "9.0", + provisioning_profile = "Wallet.mobileprovision", + infoplists = [ + ":Info.plist", + ":VersionInfoPlist", + ], + frameworks = [ + ":AsyncDisplayKitFramework", + ], + deps = [ + ":Main", + ":Lib", + ], +) diff --git a/Wallet/Info.plist b/Wallet/Info.plist index 7646b3381e..7e6af0fec1 100644 --- a/Wallet/Info.plist +++ b/Wallet/Info.plist @@ -7,7 +7,7 @@ CFBundleDevelopmentRegion en CFBundleDisplayName - ${APP_NAME} + ${PRODUCT_NAME} CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIcons diff --git a/Wallet/Sources/AppDelegate.swift b/Wallet/Sources/AppDelegate.swift index cefa741684..6eb23fdcaa 100644 --- a/Wallet/Sources/AppDelegate.swift +++ b/Wallet/Sources/AppDelegate.swift @@ -765,7 +765,7 @@ final class AppDelegate: NSObject, UIApplicationDelegate { beginWithController(infoScreen) }) } else { - let createdScreen = WalletSplashScreen(context: walletContext, mode: .created(record.info, nil), walletCreatedPreloadState: nil) + let createdScreen = WalletSplashScreen(context: walletContext, mode: .created(walletInfo: record.info, words: nil), walletCreatedPreloadState: nil) beginWithController(createdScreen) } } else { diff --git a/Wallet/Strings/en.lproj/Localizable.strings b/Wallet/Strings/en.lproj/Localizable.strings index 83e12f54a4..94b0aa8ad2 100644 --- a/Wallet/Strings/en.lproj/Localizable.strings +++ b/Wallet/Strings/en.lproj/Localizable.strings @@ -62,6 +62,7 @@ "Wallet.Send.OwnAddressAlertProceed" = "Proceed"; "Wallet.Send.TransactionInProgress" = "Please wait until the current transaction is completed."; "Wallet.Send.SyncInProgress" = "Please wait while the wallet finishes syncing with the TON Blockchain."; +"Wallet.Send.EncryptComment" = "Encrypt Text"; "Wallet.Settings.Title" = "Settings"; "Wallet.Settings.Configuration" = "Server Settings"; "Wallet.Settings.ConfigurationInfo" = "Advanced Settings"; diff --git a/Wallet/SupportFiles/Empty.swift b/Wallet/SupportFiles/Empty.swift index 8b13789179..e69de29bb2 100644 --- a/Wallet/SupportFiles/Empty.swift +++ b/Wallet/SupportFiles/Empty.swift @@ -1 +0,0 @@ - diff --git a/Wallet/Wallet.mobileprovision b/Wallet/Wallet.mobileprovision new file mode 100644 index 0000000000..879842ec7b Binary files /dev/null and b/Wallet/Wallet.mobileprovision differ diff --git a/submodules/HockeySDK-iOS/Support/HockeySDKTests/Fixtures/live_report_empty.plcrash b/Wallet/configuration.bzl similarity index 100% rename from submodules/HockeySDK-iOS/Support/HockeySDKTests/Fixtures/live_report_empty.plcrash rename to Wallet/configuration.bzl diff --git a/build-input/BUILD b/build-input/BUILD new file mode 100644 index 0000000000..e69de29bb2 diff --git a/build-system/BUILD b/build-system/BUILD new file mode 100644 index 0000000000..e69de29bb2 diff --git a/build-system/bazel-rules/apple_support b/build-system/bazel-rules/apple_support new file mode 160000 index 0000000000..501b4afb27 --- /dev/null +++ b/build-system/bazel-rules/apple_support @@ -0,0 +1 @@ +Subproject commit 501b4afb27745c4813a88ffa28acd901408014e4 diff --git a/build-system/bazel-rules/rules_apple b/build-system/bazel-rules/rules_apple new file mode 160000 index 0000000000..6e1f592277 --- /dev/null +++ b/build-system/bazel-rules/rules_apple @@ -0,0 +1 @@ +Subproject commit 6e1f592277650a2727b6e84705ec1a2dc17764fa diff --git a/build-system/bazel-rules/rules_swift b/build-system/bazel-rules/rules_swift new file mode 160000 index 0000000000..bbe187c4b1 --- /dev/null +++ b/build-system/bazel-rules/rules_swift @@ -0,0 +1 @@ +Subproject commit bbe187c4b1f55c0974a0da345e5e313eaed37c05 diff --git a/build-system/copy-provisioning-profiles.sh b/build-system/copy-provisioning-profiles.sh new file mode 100755 index 0000000000..ef676cc5b5 --- /dev/null +++ b/build-system/copy-provisioning-profiles.sh @@ -0,0 +1,125 @@ +#!/bin/sh + +copy_provisioning_profiles () { + if [ "$CODESIGNING_DATA_PATH" = "" ]; then + >&2 echo "CODESIGNING_DATA_PATH not defined" + exit 1 + fi + + + PROFILES_TYPE="$1" + case "$PROFILES_TYPE" in + development) + EXPECTED_VARIABLES=(\ + DEVELOPMENT_PROVISIONING_PROFILE_APP \ + DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_SHARE \ + DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_WIDGET \ + DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_NOTIFICATIONSERVICE \ + DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_NOTIFICATIONCONTENT \ + DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_INTENTS \ + DEVELOPMENT_PROVISIONING_PROFILE_WATCH_APP \ + DEVELOPMENT_PROVISIONING_PROFILE_WATCH_EXTENSION \ + ) + ;; + distribution) + EXPECTED_VARIABLES=(\ + DISTRIBUTION_PROVISIONING_PROFILE_APP \ + DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_SHARE \ + DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_WIDGET \ + DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_NOTIFICATIONSERVICE \ + DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_NOTIFICATIONCONTENT \ + DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_INTENTS \ + DISTRIBUTION_PROVISIONING_PROFILE_WATCH_APP \ + DISTRIBUTION_PROVISIONING_PROFILE_WATCH_EXTENSION \ + ) + ;; + *) + echo "Unknown build provisioning type: $PROFILES_TYPE" + exit 1 + ;; + esac + + EXPECTED_VARIABLE_NAMES=(\ + Telegram \ + Share \ + Widget \ + NotificationService \ + NotificationContent \ + Intents \ + WatchApp \ + WatchExtension \ + ) + + local SEARCH_NAMES=() + + local MISSING_VARIABLES="0" + for VARIABLE_NAME in ${EXPECTED_VARIABLES[@]}; do + if [ "${!VARIABLE_NAME}" = "" ]; then + echo "$VARIABLE_NAME not defined" + MISSING_VARIABLES="1" + fi + done + + if [ "$MISSING_VARIABLES" == "1" ]; then + exit 1 + fi + + local VARIABLE_COUNT=${#EXPECTED_VARIABLES[@]} + for (( i=0; i<$VARIABLE_COUNT; i=i+1 )); do + VARIABLE_NAME="${EXPECTED_VARIABLES[$(($i))]}" + SEARCH_NAMES=("${SEARCH_NAMES[@]}" "${EXPECTED_VARIABLE_NAMES[$i]}" "${!VARIABLE_NAME}") + done + + local DATA_PATH="build-input/data" + + local OUTPUT_DIRECTORY="$DATA_PATH/provisioning-profiles" + rm -rf "$OUTPUT_DIRECTORY" + mkdir -p "$OUTPUT_DIRECTORY" + + local BUILD_PATH="$OUTPUT_DIRECTORY/BUILD" + touch "$BUILD_PATH" + + echo "exports_files([" >> "$BUILD_PATH" + + local ELEMENT_COUNT=${#SEARCH_NAMES[@]} + local REMAINDER=$(($ELEMENT_COUNT % 2)) + + if [ $REMAINDER != 0 ]; then + >&2 echo "Expecting key-value pairs" + exit 1 + fi + + for PROFILE in `find "$CODESIGNING_DATA_PATH" -type f -name "*.mobileprovision"`; do + PROFILE_DATA=$(security cms -D -i "$PROFILE") + PROFILE_NAME=$(/usr/libexec/PlistBuddy -c "Print :Name" /dev/stdin <<< $(echo $PROFILE_DATA)) + for (( i=0; i<$ELEMENT_COUNT; i=i+2 )); do + ID=${SEARCH_NAMES[$i]} + SEARCH_NAME=${SEARCH_NAMES[$(($i + 1))]} + if [ "$PROFILE_NAME" = "$SEARCH_NAME" ]; then + VARIABLE_NAME="FOUND_PROFILE_$ID" + if [ "${!VARIABLE_NAME}" = "" ]; then + eval "FOUND_PROFILE_$ID=\"$PROFILE\"" + else + >&2 echo "Found multiple profiles with name \"$SEARCH_NAME\"" + exit 1 + fi + fi + done + done + + for (( i=0; i<$ELEMENT_COUNT; i=i+2 )); do + ID=${SEARCH_NAMES[$i]} + SEARCH_NAME=${SEARCH_NAMES[$(($i + 1))]} + VARIABLE_NAME="FOUND_PROFILE_$ID" + FOUND_PROFILE="${!VARIABLE_NAME}" + if [ "$FOUND_PROFILE" = "" ]; then + >&2 echo "Profile \"$SEARCH_NAME\" not found" + exit 1 + fi + + cp "$FOUND_PROFILE" "$OUTPUT_DIRECTORY/$ID.mobileprovision" + echo " \"$ID.mobileprovision\"," >> $BUILD_PATH + done + + echo "])" >> "$BUILD_PATH" +} diff --git a/build-system/defines.bzl b/build-system/defines.bzl new file mode 100644 index 0000000000..d9e5ac9fdd --- /dev/null +++ b/build-system/defines.bzl @@ -0,0 +1,36 @@ +def string_value(ctx, define_name): + """Looks up a define on ctx for a string value. + + Will also report an error if the value is not defined. + + Args: + ctx: A skylark context. + define_name: The name of the define to look up. + + Returns: + The value of the define. + """ + value = ctx.var.get(define_name, None) + if value != None: + return value + fail("Expected value for --define={} was not found".format( + define_name, + )) + +def _file_from_define(ctx): + output = ctx.outputs.out + ctx.actions.write( + output = output, + content = "profile_data", + ) + +file_from_define = rule( + implementation = _file_from_define, + attrs = { + "define_name": attr.string(mandatory = True), + "extension": attr.string(mandatory = True), + }, + outputs = { + "out": "%{name}.%{extension}" + }, +) diff --git a/build-system/generate-xcode-project.sh b/build-system/generate-xcode-project.sh new file mode 100755 index 0000000000..4cc7c0d8b1 --- /dev/null +++ b/build-system/generate-xcode-project.sh @@ -0,0 +1,95 @@ +#!/bin/sh + +set -e + +BAZEL="$(which bazel)" +if [ "$BAZEL" = "" ]; then + echo "bazel not found in PATH" + exit 1 +fi + +XCODE_VERSION=$(cat "build-system/xcode_version") +INSTALLED_XCODE_VERSION=$(echo `plutil -p \`xcode-select -p\`/../Info.plist | grep -e CFBundleShortVersionString | sed 's/[^0-9\.]*//g'`) + +if [ "$INSTALLED_XCODE_VERSION" != "$XCODE_VERSION" ]; then + echo "Xcode $XCODE_VERSION required, $INSTALLED_XCODE_VERSION installed (at $(xcode-select -p))" + exit 1 +fi + +EXPECTED_VARIABLES=(\ + BUILD_NUMBER \ + APP_VERSION \ + BUNDLE_ID \ + DEVELOPMENT_TEAM \ + API_ID \ + API_HASH \ + APP_CENTER_ID \ + IS_INTERNAL_BUILD \ + IS_APPSTORE_BUILD \ + APPSTORE_ID \ + APP_SPECIFIC_URL_SCHEME \ +) + +MISSING_VARIABLES="0" +for VARIABLE_NAME in ${EXPECTED_VARIABLES[@]}; do + if [ "${!VARIABLE_NAME}" = "" ]; then + echo "$VARIABLE_NAME not defined" + MISSING_VARIABLES="1" + fi +done +if [ "$MISSING_VARIABLES" == "1" ]; then + exit 1 +fi + +GEN_DIRECTORY="build-input/gen/project" +rm -rf "$GEN_DIRECTORY" +mkdir -p "$GEN_DIRECTORY" + +pushd "build-system/tulsi" +"$BAZEL" build //:tulsi --xcode_version="$XCODE_VERSION" +popd + +TULSI_DIRECTORY="build-input/gen/project" +TULSI_APP="build-input/gen/project/Tulsi.app" +TULSI="$TULSI_APP/Contents/MacOS/Tulsi" +mkdir -p "$TULSI_DIRECTORY" + +unzip -oq "build-system/tulsi/bazel-bin/tulsi.zip" -d "$TULSI_DIRECTORY" + +CORE_COUNT=$(sysctl -n hw.logicalcpu) +CORE_COUNT_MINUS_ONE=$(expr ${CORE_COUNT} \- 1) + +BAZEL_OPTIONS=(\ + --features=swift.use_global_module_cache \ + --spawn_strategy=standalone \ + --strategy=SwiftCompile=standalone \ + --features=swift.enable_batch_mode \ + --swiftcopt=-j${CORE_COUNT_MINUS_ONE} \ +) + +if [ "$BAZEL_CACHE_DIR" != "" ]; then + BAZEL_OPTIONS=("${BAZEL_OPTIONS[@]}" --disk_cache="$(echo $BAZEL_CACHE_DIR | sed -e 's/[\/&]/\\&/g')") +fi + +"$TULSI" -- \ + --verbose \ + --create-tulsiproj Telegram \ + --workspaceroot ./ \ + --bazel "$BAZEL" \ + --outputfolder "$GEN_DIRECTORY" \ + --target Telegram:Telegram \ + --target Telegram:Main \ + --target Telegram:Lib \ + +PATCH_OPTIONS="BazelBuildOptionsDebug BazelBuildOptionsRelease" +for NAME in $PATCH_OPTIONS; do + sed -i "" -e '1h;2,$H;$!d;g' -e 's/\("'"$NAME"'" : {\n[ ]*"p" : "$(inherited)\)/\1'" ${BAZEL_OPTIONS[*]}"'/' "$GEN_DIRECTORY/Telegram.tulsiproj/Configs/Telegram.tulsigen" +done + +sed -i "" -e '1h;2,$H;$!d;g' -e 's/\("sourceFilters" : \[\n[ ]*\)"\.\/\.\.\."/\1"Telegram\/...", "submodules\/..."/' "$GEN_DIRECTORY/Telegram.tulsiproj/Configs/Telegram.tulsigen" + +"$TULSI" -- \ + --verbose \ + --genconfig "$GEN_DIRECTORY/Telegram.tulsiproj:Telegram" \ + --bazel "$BAZEL" \ + --outputfolder "$GEN_DIRECTORY" \ diff --git a/build-system/manage-developer-portal-app.sh b/build-system/manage-developer-portal-app.sh new file mode 100644 index 0000000000..60e21c1381 --- /dev/null +++ b/build-system/manage-developer-portal-app.sh @@ -0,0 +1,129 @@ +#!/bin/bash + +set -e + +FASTLANE="$(which fastlane)" + +EXPECTED_VARIABLES=(\ + APPLE_ID \ + BASE_BUNDLE_ID \ + APP_NAME \ + TEAM_ID \ + PROVISIONING_DIRECTORY \ +) + +MISSING_VARIABLES="0" +for VARIABLE_NAME in ${EXPECTED_VARIABLES[@]}; do + if [ "${!VARIABLE_NAME}" = "" ]; then + echo "$VARIABLE_NAME not defined" + MISSING_VARIABLES="1" + fi +done +if [ "$MISSING_VARIABLES" == "1" ]; then + exit 1 +fi + +if [ ! -d "$PROVISIONING_DIRECTORY" ]; then + echo "Directory $PROVISIONING_DIRECTORY does not exist" + exit 1 +fi + +BASE_DIR=$(mktemp -d) +FASTLANE_DIR="$BASE_DIR/fastlane" +mkdir "$FASTLANE_DIR" +FASTFILE="$FASTLANE_DIR/Fastfile" + +touch "$FASTFILE" + +CREDENTIALS=(\ + --username "$APPLE_ID" \ + --team_id "$TEAM_ID" \ +) +export FASTLANE_SKIP_UPDATE_CHECK=1 + +APP_EXTENSIONS=(\ + Share \ + SiriIntents \ + NotificationContent \ + NotificationService \ + Widget \ +) + +echo "lane :manage_app do" >> "$FASTFILE" +echo " produce(" >> "$FASTFILE" +echo " username: '$APPLE_ID'," >> "$FASTFILE" +echo " app_identifier: '${BASE_BUNDLE_ID}'," >> "$FASTFILE" +echo " app_name: '$APP_NAME'," >> "$FASTFILE" +echo " language: 'English'," >> "$FASTFILE" +echo " app_version: '1.0'," >> "$FASTFILE" +echo " team_id: '$TEAM_ID'," >> "$FASTFILE" +echo " skip_itc: true," >> "$FASTFILE" +echo " )" >> "$FASTFILE" + +echo " produce(" >> "$FASTFILE" +echo " username: '$APPLE_ID'," >> "$FASTFILE" +echo " app_identifier: '${BASE_BUNDLE_ID}.watchkitapp'," >> "$FASTFILE" +echo " app_name: '$APP_NAME Watch App'," >> "$FASTFILE" +echo " language: 'English'," >> "$FASTFILE" +echo " app_version: '1.0'," >> "$FASTFILE" +echo " team_id: '$TEAM_ID'," >> "$FASTFILE" +echo " skip_itc: true," >> "$FASTFILE" +echo " )" >> "$FASTFILE" + +echo " produce(" >> "$FASTFILE" +echo " username: '$APPLE_ID'," >> "$FASTFILE" +echo " app_identifier: '${BASE_BUNDLE_ID}.watchkitapp.watchkitextension'," >> "$FASTFILE" +echo " app_name: '$APP_NAME Watch App Extension'," >> "$FASTFILE" +echo " language: 'English'," >> "$FASTFILE" +echo " app_version: '1.0'," >> "$FASTFILE" +echo " team_id: '$TEAM_ID'," >> "$FASTFILE" +echo " skip_itc: true," >> "$FASTFILE" +echo " )" >> "$FASTFILE" + +for EXTENSION in ${APP_EXTENSIONS[@]}; do + echo " produce(" >> "$FASTFILE" + echo " username: '$APPLE_ID'," >> "$FASTFILE" + echo " app_identifier: '${BASE_BUNDLE_ID}.${EXTENSION}'," >> "$FASTFILE" + echo " app_name: '${APP_NAME} ${EXTENSION}'," >> "$FASTFILE" + echo " language: 'English'," >> "$FASTFILE" + echo " app_version: '1.0'," >> "$FASTFILE" + echo " team_id: '$TEAM_ID'," >> "$FASTFILE" + echo " skip_itc: true," >> "$FASTFILE" + echo " )" >> "$FASTFILE" +done + +echo "end" >> "$FASTFILE" + +pushd "$BASE_DIR" + +fastlane cert ${CREDENTIALS[@]} --development + +fastlane manage_app + +fastlane produce group -g "group.$BASE_BUNDLE_ID" -n "$APP_NAME Group" ${CREDENTIALS[@]} + +fastlane produce enable_services -a "$BASE_BUNDLE_ID" ${CREDENTIALS[@]} \ + --app-group \ + --push-notification \ + --sirikit + +fastlane produce associate_group -a "$BASE_BUNDLE_ID" "group.$BASE_BUNDLE_ID" ${CREDENTIALS[@]} +for EXTENSION in ${APP_EXTENSIONS[@]}; do + fastlane produce enable_services -a "${BASE_BUNDLE_ID}.${EXTENSION}" ${CREDENTIALS[@]} \ + --app-group + + fastlane produce associate_group -a "${BASE_BUNDLE_ID}.${EXTENSION}" "group.$BASE_BUNDLE_ID" ${CREDENTIALS[@]} +done + +for DEVELOPMENT_FLAG in "--development"; do + fastlane sigh -a "$BASE_BUNDLE_ID" ${CREDENTIALS[@]} -o "$PROVISIONING_DIRECTORY" $DEVELOPMENT_FLAG \ + --skip_install + for EXTENSION in ${APP_EXTENSIONS[@]}; do + fastlane sigh -a "${BASE_BUNDLE_ID}.${EXTENSION}" ${CREDENTIALS[@]} -o "$PROVISIONING_DIRECTORY" $DEVELOPMENT_FLAG \ + --skip_install + done +done + +popd + +rm -rf "$BASE_DIR" diff --git a/build-system/plist_fragment.bzl b/build-system/plist_fragment.bzl new file mode 100644 index 0000000000..4b28670773 --- /dev/null +++ b/build-system/plist_fragment.bzl @@ -0,0 +1,52 @@ +load("//build-system:defines.bzl", + "string_value", +) + +def _plist_fragment(ctx): + output = ctx.outputs.out + + found_keys = list() + template = ctx.attr.template + current_start = 0 + for i in range(len(template)): + start_index = template.find("{", current_start) + if start_index == -1: + break + end_index = template.find("}", start_index + 1) + if end_index == -1: + fail("Could not find the matching '}' for the '{' at {}".format(start_index)) + found_keys.append(template[start_index + 1:end_index]) + current_start = end_index + 1 + + resolved_values = dict() + for key in found_keys: + value = ctx.var.get(key, None) + if value == None: + fail("Expected value for --define={} was not found".format(key)) + resolved_values[key] = value + + plist_string = """ + + + + + """ + template.format(**resolved_values) + """ + + + """ + + ctx.actions.write( + output = output, + content = plist_string, + ) + +plist_fragment = rule( + implementation = _plist_fragment, + attrs = { + "extension": attr.string(mandatory = True), + "template": attr.string(mandatory = True), + }, + outputs = { + "out": "%{name}.%{extension}" + }, +) diff --git a/build-system/prepare-build-variables.sh b/build-system/prepare-build-variables.sh new file mode 100755 index 0000000000..d88042f518 --- /dev/null +++ b/build-system/prepare-build-variables.sh @@ -0,0 +1,69 @@ +#!/bin/sh + +set -e + +prepare_build_variables () { + BUILD_TYPE="$1" + case "$BUILD_TYPE" in + development) + APS_ENVIRONMENT="development" + ;; + distribution) + APS_ENVIRONMENT="production" + ;; + *) + echo "Unknown build provisioning type: $BUILD_TYPE" + exit 1 + ;; + esac + + local BAZEL="$(which bazel)" + if [ "$BAZEL" = "" ]; then + echo "bazel not found in PATH" + exit 1 + fi + + local EXPECTED_VARIABLES=(\ + BUILD_NUMBER \ + APP_VERSION \ + BUNDLE_ID \ + DEVELOPMENT_TEAM \ + API_ID \ + API_HASH \ + APP_CENTER_ID \ + IS_INTERNAL_BUILD \ + IS_APPSTORE_BUILD \ + APPSTORE_ID \ + APP_SPECIFIC_URL_SCHEME \ + ) + + local MISSING_VARIABLES="0" + for VARIABLE_NAME in ${EXPECTED_VARIABLES[@]}; do + if [ "${!VARIABLE_NAME}" = "" ]; then + echo "$VARIABLE_NAME not defined" + MISSING_VARIABLES="1" + fi + done + + if [ "$MISSING_VARIABLES" == "1" ]; then + exit 1 + fi + + local VARIABLES_DIRECTORY="build-input/data" + mkdir -p "$VARIABLES_DIRECTORY" + local VARIABLES_PATH="$VARIABLES_DIRECTORY/variables.bzl" + rm -f "$VARIABLES_PATH" + + echo "telegram_build_number = \"$BUILD_NUMBER\"" >> "$VARIABLES_PATH" + echo "telegram_version = \"$APP_VERSION\"" >> "$VARIABLES_PATH" + echo "telegram_bundle_id = \"$BUNDLE_ID\"" >> "$VARIABLES_PATH" + echo "telegram_api_id = \"$API_ID\"" >> "$VARIABLES_PATH" + echo "telegram_team_id = \"$DEVELOPMENT_TEAM\"" >> "$VARIABLES_PATH" + echo "telegram_api_hash = \"$API_HASH\"" >> "$VARIABLES_PATH" + echo "telegram_app_center_id = \"$APP_CENTER_ID\"" >> "$VARIABLES_PATH" + echo "telegram_is_internal_build = \"$IS_INTERNAL_BUILD\"" >> "$VARIABLES_PATH" + echo "telegram_is_appstore_build = \"$IS_APPSTORE_BUILD\"" >> "$VARIABLES_PATH" + echo "telegram_appstore_id = \"$APPSTORE_ID\"" >> "$VARIABLES_PATH" + echo "telegram_app_specific_url_scheme = \"$APP_SPECIFIC_URL_SCHEME\"" >> "$VARIABLES_PATH" + echo "telegram_aps_environment = \"$APS_ENVIRONMENT\"" >> "$VARIABLES_PATH" +} diff --git a/build-system/prepare-build.sh b/build-system/prepare-build.sh new file mode 100755 index 0000000000..c65df066f1 --- /dev/null +++ b/build-system/prepare-build.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +set -e + +BUILD_TYPE="$1" +case "$BUILD_TYPE" in + development) + PROFILES_TYPE="development" + ;; + distribution) + PROFILES_TYPE="distribution" + ;; + *) + echo "Unknown build provisioning type: $BUILD_TYPE" + exit 1 + ;; +esac + +BASE_PATH=$(dirname $0) + +DATA_DIRECTORY="build-input/data" +rm -rf "$DATA_DIRECTORY" +mkdir -p "$DATA_DIRECTORY" +touch "$DATA_DIRECTORY/BUILD" + +source "$BASE_PATH/copy-provisioning-profiles.sh" +source "$BASE_PATH/prepare-build-variables.sh" + +echo "Copying provisioning profiles..." +copy_provisioning_profiles "$PROFILES_TYPE" + +echo "Preparing build variables..." +prepare_build_variables "$BUILD_TYPE" diff --git a/build-system/tulsi b/build-system/tulsi new file mode 160000 index 0000000000..ee185c4c20 --- /dev/null +++ b/build-system/tulsi @@ -0,0 +1 @@ +Subproject commit ee185c4c20ea4384bc3cbf8ccd8705c904154abb diff --git a/build-system/unique_directories.bzl b/build-system/unique_directories.bzl new file mode 100644 index 0000000000..a2ff428504 --- /dev/null +++ b/build-system/unique_directories.bzl @@ -0,0 +1,10 @@ + +def unique_directories(paths): + result = [] + for path in paths: + index = path.rfind("/") + if index != -1: + directory = path[:index] + if not directory in result: + result.append(directory) + return result diff --git a/build-system/verify.sh b/build-system/verify.sh index d260604e30..89e33bef02 100644 --- a/build-system/verify.sh +++ b/build-system/verify.sh @@ -21,22 +21,22 @@ if [ -z "$BUILD_NUMBER" ]; then exit 1 fi -export ENTITLEMENTS_APP="Telegram/Telegram-iOS/Telegram-iOS-AppStoreLLC.entitlements" +export ENTITLEMENTS_APP="Telegram-iOS/Telegram-iOS-AppStoreLLC.entitlements" export DEVELOPMENT_PROVISIONING_PROFILE_APP="match Development ph.telegra.Telegraph" export DISTRIBUTION_PROVISIONING_PROFILE_APP="match AppStore ph.telegra.Telegraph" -export ENTITLEMENTS_EXTENSION_SHARE="Telegram/Share/Share-AppStoreLLC.entitlements" +export ENTITLEMENTS_EXTENSION_SHARE="Share/Share-AppStoreLLC.entitlements" export DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_SHARE="match Development ph.telegra.Telegraph.Share" export DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_SHARE="match AppStore ph.telegra.Telegraph.Share" -export ENTITLEMENTS_EXTENSION_WIDGET="Telegram/Widget/Widget-AppStoreLLC.entitlements" +export ENTITLEMENTS_EXTENSION_WIDGET="Widget/Widget-AppStoreLLC.entitlements" export DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_WIDGET="match Development ph.telegra.Telegraph.Widget" export DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_WIDGET="match AppStore ph.telegra.Telegraph.Widget" -export ENTITLEMENTS_EXTENSION_NOTIFICATIONSERVICE="Telegram/NotificationService/NotificationService-AppStoreLLC.entitlements" +export ENTITLEMENTS_EXTENSION_NOTIFICATIONSERVICE="NotificationService/NotificationService-AppStoreLLC.entitlements" export DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_NOTIFICATIONSERVICE="match Development ph.telegra.Telegraph.NotificationService" export DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_NOTIFICATIONSERVICE="match AppStore ph.telegra.Telegraph.NotificationService" -export ENTITLEMENTS_EXTENSION_NOTIFICATIONCONTENT="Telegram/NotificationContent/NotificationContent-AppStoreLLC.entitlements" +export ENTITLEMENTS_EXTENSION_NOTIFICATIONCONTENT="NotificationContent/NotificationContent-AppStoreLLC.entitlements" export DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_NOTIFICATIONCONTENT="match Development ph.telegra.Telegraph.NotificationContent" export DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_NOTIFICATIONCONTENT="match AppStore ph.telegra.Telegraph.NotificationContent" -export ENTITLEMENTS_EXTENSION_INTENTS="Telegram/SiriIntents/SiriIntents-AppStoreLLC.entitlements" +export ENTITLEMENTS_EXTENSION_INTENTS="SiriIntents/SiriIntents-AppStoreLLC.entitlements" export DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_INTENTS="match Development ph.telegra.Telegraph.SiriIntents" export DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_INTENTS="match AppStore ph.telegra.Telegraph.SiriIntents" export DEVELOPMENT_PROVISIONING_PROFILE_WATCH_APP="match Development ph.telegra.Telegraph.watchkitapp" diff --git a/build-system/xcode_version b/build-system/xcode_version new file mode 100644 index 0000000000..f226094f1f --- /dev/null +++ b/build-system/xcode_version @@ -0,0 +1 @@ +11.3.1 \ No newline at end of file diff --git a/fastlane/Fastfile b/fastlane/Fastfile deleted file mode 100644 index 9d9c295348..0000000000 --- a/fastlane/Fastfile +++ /dev/null @@ -1,65 +0,0 @@ -fastlane_version "1.81.0" - -default_platform :ios - -base_app_identifier_llc = "ph.telegra.Telegraph" -app_identifier_llc = [ - base_app_identifier_llc, - base_app_identifier_llc + ".Widget", - base_app_identifier_llc + ".NotificationContent", - base_app_identifier_llc + ".SiriIntents", - base_app_identifier_llc + ".Share", - base_app_identifier_llc + ".watchkitapp", - base_app_identifier_llc + ".watchkitapp.watchkitextension", - base_app_identifier_llc + ".NotificationService" -] -signing_identity_llc = "iPhone Distribution: Digital Fortress LLC (C67CF9S4VU)" - -lane :do_build_app do |options| - puts("Building with build number: " + options[:build_number] + ", commit id: " + options[:commit_id]) - gym( - workspace: "Telegram-iOS.xcworkspace", - configuration: options[:configuration], - scheme: options[:scheme], - silent: false, - clean: true, - export_method: options[:export_method], - output_name: options[:scheme], - derived_data_path: "build/" + options[:scheme] + "/DerivedData", - xcargs: "BUILD_NUMBER='" + options[:build_number] + "' " + "COMMIT_ID='" + options[:commit_id] + "'", - archive_path: "build/" + options[:scheme] + "/Archive", - export_options: { - compileBitcode: false, - iCloudContainerEnvironment: "Production", - provisioningProfiles: options[:provisioningProfiles], - stripSwiftSymbols: true, - uploadBitcode: false, - signingCertificate: options[:signingCertificate] - } - ) -end - -lane :build_for_appstore do |options| - do_build_app( - configuration: "ReleaseAppStoreLLC", - scheme: "Telegram-iOS-AppStoreLLC", - export_method: "app-store", - build_number: options[:build_number], - commit_id: options[:commit_hash], - signingCertificate: signing_identity_llc, - provisioningProfiles: { - base_app_identifier_llc => "match AppStore " + base_app_identifier_llc, - base_app_identifier_llc + ".Share" => "match AppStore " + base_app_identifier_llc + ".Share", - base_app_identifier_llc + ".SiriIntents" => "match AppStore " + base_app_identifier_llc + ".SiriIntents", - base_app_identifier_llc + ".Widget" => "match AppStore " + base_app_identifier_llc + ".Widget", - base_app_identifier_llc + ".NotificationContent" => "match AppStore " + base_app_identifier_llc + ".NotificationContent", - base_app_identifier_llc + ".watchkitapp.watchkitextension" => "match AppStore " + base_app_identifier_llc + ".watchkitapp.watchkitextension", - base_app_identifier_llc + ".watchkitapp" => "match AppStore " + base_app_identifier_llc + ".watchkitapp", - base_app_identifier_llc + ".NotificationService" => "match AppStore " + base_app_identifier_llc + ".NotificationService" - } - ) -end - -if File.exists?("../buildbox/transient-data/telegram-ios-shared/fastlane/Fastfile") - import "../buildbox/transient-data/telegram-ios-shared/fastlane/Fastfile" -end diff --git a/package_app.sh b/package_app.sh index cbf6961a69..bd0c12db93 100644 --- a/package_app.sh +++ b/package_app.sh @@ -1,6 +1,5 @@ #!/bin/sh -#set -x set -e if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then @@ -55,7 +54,7 @@ mkdir -p "$TEMP_ENTITLEMENTS_PATH" if [ "$APP_TYPE" == "wallet" ]; then cp "buck-out/gen/Wallet/AppPackage#$PLATFORM_FLAVORS.ipa" "$IPA_PATH.original" else - cp "buck-out/gen/AppPackage#$PLATFORM_FLAVORS.ipa" "$IPA_PATH.original" + cp "buck-out/gen/Telegram/AppPackage#$PLATFORM_FLAVORS.ipa" "$IPA_PATH.original" fi rm -rf "$IPA_PATH.original.unpacked" rm -f "$BUILD_PATH/${APP_NAME}_signed.ipa" @@ -278,7 +277,7 @@ APP_PLIST="$APP_PATH/Info.plist" if [ "$APP_TYPE" == "wallet" ]; then APP_BINARY_TARGET="//Wallet:Wallet" else - APP_BINARY_TARGET="//:Telegram" + APP_BINARY_TARGET="//Telegram:Telegram" fi echo "Repacking frameworks..." @@ -312,7 +311,7 @@ done if [ "$APP_TYPE" == "wallet" ]; then APP_BINARY_DSYM_PATH="buck-out/gen/Wallet/Wallet#dwarf-and-dsym,$PLATFORM_FLAVORS,no-include-frameworks/Wallet.app.dSYM" else - APP_BINARY_DSYM_PATH="buck-out/gen/Telegram#dwarf-and-dsym,$PLATFORM_FLAVORS,no-include-frameworks/Telegram.app.dSYM" + APP_BINARY_DSYM_PATH="buck-out/gen/Telegram/Telegram#dwarf-and-dsym,$PLATFORM_FLAVORS,no-include-frameworks/Telegram.app.dSYM" fi cp -r "$APP_BINARY_DSYM_PATH" "$DSYMS_DIR/" @@ -323,12 +322,12 @@ else fi for EXTENSION in $EXTENSIONS; do - EXTENSION_DSYM_PATH="buck-out/gen/${EXTENSION}Extension#dwarf-and-dsym,$PLATFORM_FLAVORS,no-include-frameworks/${EXTENSION}Extension.appex.dSYM" + EXTENSION_DSYM_PATH="buck-out/gen/Telegram/${EXTENSION}Extension#dwarf-and-dsym,$PLATFORM_FLAVORS,no-include-frameworks/${EXTENSION}Extension.appex.dSYM" cp -r "$EXTENSION_DSYM_PATH" "$DSYMS_DIR/" done if [ "$APP_TYPE" != "wallet" ]; then - WATCH_EXTENSION_DSYM_PATH="buck-out/gen/WatchAppExtension#dwarf-and-dsym,no-include-frameworks,watchos-arm64_32,watchos-armv7k/WatchAppExtension.appex.dSYM" + WATCH_EXTENSION_DSYM_PATH="buck-out/gen/Telegram/WatchAppExtension#dwarf-and-dsym,no-include-frameworks,watchos-arm64_32,watchos-armv7k/WatchAppExtension.appex.dSYM" cp -r "$WATCH_EXTENSION_DSYM_PATH" "$DSYMS_DIR/" fi diff --git a/submodules/AccountContext/BUCK b/submodules/AccountContext/BUCK index 6a3a3d431f..120fec2a7e 100644 --- a/submodules/AccountContext/BUCK +++ b/submodules/AccountContext/BUCK @@ -16,7 +16,7 @@ static_library( "//submodules/Postbox:Postbox#shared", "//submodules/TelegramCore:TelegramCore#shared", "//submodules/SyncCore:SyncCore#shared", - #"//submodules/WalletCore:WalletCore", + "//submodules/WalletCore:WalletCore", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/AccountContext/BUILD b/submodules/AccountContext/BUILD new file mode 100644 index 0000000000..fd5976b7f7 --- /dev/null +++ b/submodules/AccountContext/BUILD @@ -0,0 +1,25 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "AccountContext", + module_name = "AccountContext", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/TelegramAudio:TelegramAudio", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/TemporaryCachedPeerDataManager:TemporaryCachedPeerDataManager", + "//submodules/DeviceLocationManager:DeviceLocationManager", + "//submodules/MediaPlayer:UniversalMediaPlayer", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/SyncCore:SyncCore", + "//submodules/WalletCore:WalletCore", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/AccountContext/Info.plist b/submodules/AccountContext/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/AccountContext/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index cb51c254cc..28b26239f2 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1,10 +1,12 @@ import Foundation +import UIKit import Postbox import TelegramCore import SyncCore import TelegramPresentationData import TelegramUIPreferences import SwiftSignalKit +import AsyncDisplayKit import Display import DeviceLocationManager import TemporaryCachedPeerDataManager @@ -262,6 +264,7 @@ public enum PeerInfoControllerMode { case generic case calls(messages: [Message]) case nearbyPeer + case group(PeerId) } public enum ContactListActionItemInlineIconPosition { diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index da9d89bc63..4ecd45faa4 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -1,8 +1,10 @@ import Foundation +import UIKit import Postbox import TelegramCore import SyncCore import TextFormat +import AsyncDisplayKit import Display import SwiftSignalKit import TelegramPresentationData diff --git a/submodules/AccountContext/Sources/ChatListController.swift b/submodules/AccountContext/Sources/ChatListController.swift index f0e4304440..ad9ac4ee69 100644 --- a/submodules/AccountContext/Sources/ChatListController.swift +++ b/submodules/AccountContext/Sources/ChatListController.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import Postbox import Display diff --git a/submodules/AccountContext/Sources/ContactMultiselectionController.swift b/submodules/AccountContext/Sources/ContactMultiselectionController.swift index 0d69cfc183..a0371a2cd3 100644 --- a/submodules/AccountContext/Sources/ContactMultiselectionController.swift +++ b/submodules/AccountContext/Sources/ContactMultiselectionController.swift @@ -1,12 +1,36 @@ import Foundation +import UIKit import Display import SwiftSignalKit import Postbox +public struct ChatListNodeAdditionalCategory { + public var id: Int + public var icon: UIImage? + public var title: String + + public init(id: Int, icon: UIImage?, title: String) { + self.id = id + self.icon = icon + self.title = title + } +} + +public struct ContactMultiselectionControllerAdditionalCategories { + public var categories: [ChatListNodeAdditionalCategory] + public var selectedCategories: Set + + public init(categories: [ChatListNodeAdditionalCategory], selectedCategories: Set) { + self.categories = categories + self.selectedCategories = selectedCategories + } +} + public enum ContactMultiselectionControllerMode { case groupCreation case peerSelection(searchChatList: Bool, searchGroups: Bool, searchChannels: Bool) case channelCreation + case chatSelection(selectedChats: Set, additionalCategories: ContactMultiselectionControllerAdditionalCategories?) } public enum ContactListFilter { @@ -20,17 +44,24 @@ public final class ContactMultiselectionControllerParams { public let mode: ContactMultiselectionControllerMode public let options: [ContactListAdditionalOption] public let filters: [ContactListFilter] + public let alwaysEnabled: Bool - public init(context: AccountContext, mode: ContactMultiselectionControllerMode, options: [ContactListAdditionalOption], filters: [ContactListFilter] = [.excludeSelf]) { + public init(context: AccountContext, mode: ContactMultiselectionControllerMode, options: [ContactListAdditionalOption], filters: [ContactListFilter] = [.excludeSelf], alwaysEnabled: Bool = false) { self.context = context self.mode = mode self.options = options self.filters = filters + self.alwaysEnabled = alwaysEnabled } } +public enum ContactMultiselectionResult { + case none + case result(peerIds: [ContactListPeerId], additionalOptionIds: [Int]) +} + public protocol ContactMultiselectionController: ViewController { - var result: Signal<[ContactListPeerId], NoError> { get } + var result: Signal { get } var displayProgress: Bool { get set } var dismissed: (() -> Void)? { get set } } diff --git a/submodules/AccountContext/Sources/OpenChatMessage.swift b/submodules/AccountContext/Sources/OpenChatMessage.swift index 4ff4350a6f..39bf422b69 100644 --- a/submodules/AccountContext/Sources/OpenChatMessage.swift +++ b/submodules/AccountContext/Sources/OpenChatMessage.swift @@ -5,6 +5,7 @@ import TelegramCore import SyncCore import SwiftSignalKit import Display +import AsyncDisplayKit public enum ChatControllerInteractionOpenMessageMode { case `default` diff --git a/submodules/AccountUtils/BUILD b/submodules/AccountUtils/BUILD new file mode 100644 index 0000000000..4edfde468a --- /dev/null +++ b/submodules/AccountUtils/BUILD @@ -0,0 +1,20 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "AccountUtils", + module_name = "AccountUtils", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/SyncCore:SyncCore", + "//submodules/AccountContext:AccountContext", + "//submodules/TelegramUIPreferences:TelegramUIPreferences", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/ActionSheetPeerItem/BUILD b/submodules/ActionSheetPeerItem/BUILD new file mode 100644 index 0000000000..e2840eb207 --- /dev/null +++ b/submodules/ActionSheetPeerItem/BUILD @@ -0,0 +1,22 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ActionSheetPeerItem", + module_name = "ActionSheetPeerItem", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/SyncCore:SyncCore", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/AvatarNode:AvatarNode", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/AccountContext:AccountContext", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/ActionSheetPeerItem/Info.plist b/submodules/ActionSheetPeerItem/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/ActionSheetPeerItem/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/ActionSheetPeerItem/Sources/ActionSheetPeerItem.h b/submodules/ActionSheetPeerItem/Sources/ActionSheetPeerItem.h deleted file mode 100644 index 43128d709d..0000000000 --- a/submodules/ActionSheetPeerItem/Sources/ActionSheetPeerItem.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// ActionSheetPeerItem.h -// ActionSheetPeerItem -// -// Created by Peter on 8/5/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for ActionSheetPeerItem. -FOUNDATION_EXPORT double ActionSheetPeerItemVersionNumber; - -//! Project version string for ActionSheetPeerItem. -FOUNDATION_EXPORT const unsigned char ActionSheetPeerItemVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/ActivityIndicator/BUCK b/submodules/ActivityIndicator/BUCK index fd29559549..334ac6aecf 100644 --- a/submodules/ActivityIndicator/BUCK +++ b/submodules/ActivityIndicator/BUCK @@ -7,7 +7,7 @@ static_library( ]), deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit#shared", - "//submodules/Display:Display#shared", + "//submodules/Display:Display#shared", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/ActivityIndicator/BUILD b/submodules/ActivityIndicator/BUILD new file mode 100644 index 0000000000..08c019b637 --- /dev/null +++ b/submodules/ActivityIndicator/BUILD @@ -0,0 +1,16 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ActivityIndicator", + module_name = "ActivityIndicator", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/Display:Display", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/ActivityIndicator/Info.plist b/submodules/ActivityIndicator/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/ActivityIndicator/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/ActivityIndicator/Sources/ActivityIndicator.h b/submodules/ActivityIndicator/Sources/ActivityIndicator.h deleted file mode 100644 index d1f730c19a..0000000000 --- a/submodules/ActivityIndicator/Sources/ActivityIndicator.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// ActivityIndicator.h -// ActivityIndicator -// -// Created by Peter on 8/1/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for ActivityIndicator. -FOUNDATION_EXPORT double ActivityIndicatorVersionNumber; - -//! Project version string for ActivityIndicator. -FOUNDATION_EXPORT const unsigned char ActivityIndicatorVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/ActivityIndicator/Sources/ActivityIndicator.swift b/submodules/ActivityIndicator/Sources/ActivityIndicator.swift index 205ef1c454..0336241068 100644 --- a/submodules/ActivityIndicator/Sources/ActivityIndicator.swift +++ b/submodules/ActivityIndicator/Sources/ActivityIndicator.swift @@ -92,7 +92,6 @@ public final class ActivityIndicator: ASDisplayNode { self.indicatorNode = ASImageNode() self.indicatorNode.isLayerBacked = true - self.indicatorNode.displayWithoutProcessing = true self.indicatorNode.displaysAsynchronously = false super.init() diff --git a/submodules/AlertUI/BUILD b/submodules/AlertUI/BUILD new file mode 100644 index 0000000000..f9b4dacc69 --- /dev/null +++ b/submodules/AlertUI/BUILD @@ -0,0 +1,15 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "AlertUI", + module_name = "AlertUI", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/Display:Display", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/AlertUI/Info.plist b/submodules/AlertUI/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/AlertUI/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/AlertUI/Sources/AlertUI.h b/submodules/AlertUI/Sources/AlertUI.h deleted file mode 100644 index b188aba6c8..0000000000 --- a/submodules/AlertUI/Sources/AlertUI.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// AlertUI.h -// AlertUI -// -// Created by Peter on 8/10/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for AlertUI. -FOUNDATION_EXPORT double AlertUIVersionNumber; - -//! Project version string for AlertUI. -FOUNDATION_EXPORT const unsigned char AlertUIVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/AnimatedStickerNode/BUILD b/submodules/AnimatedStickerNode/BUILD new file mode 100644 index 0000000000..bf1ba42bdd --- /dev/null +++ b/submodules/AnimatedStickerNode/BUILD @@ -0,0 +1,20 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "AnimatedStickerNode", + module_name = "AnimatedStickerNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/YuvConversion:YuvConversion", + "//submodules/GZip:GZip", + "//submodules/rlottie:RLottieBinding", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift b/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift index 173646c596..cbff90d2f2 100644 --- a/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift +++ b/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift @@ -274,9 +274,7 @@ private final class AnimatedStickerDirectFrameSource: AnimatedStickerFrameSource self.height = height self.bytesPerRow = (4 * Int(width) + 15) & (~15) self.currentFrame = 0 - guard let rawData = TGGUnzipData(data, 8 * 1024 * 1024) else { - return nil - } + let rawData = TGGUnzipData(data, 8 * 1024 * 1024) ?? data guard let animation = LottieInstance(data: rawData, cacheKey: "") else { return nil } diff --git a/submodules/AnimationUI/BUILD b/submodules/AnimationUI/BUILD new file mode 100644 index 0000000000..adc0b2a210 --- /dev/null +++ b/submodules/AnimationUI/BUILD @@ -0,0 +1,28 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "AnimationUI", + module_name = "AnimationUI", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/SyncCore:SyncCore", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/YuvConversion:YuvConversion", + "//submodules/StickerResources:StickerResources", + "//submodules/MediaResources:MediaResources", + "//submodules/Tuples:Tuples", + "//submodules/GZip:GZip", + "//submodules/rlottie:RLottieBinding", + "//submodules/lottie-ios:Lottie", + "//submodules/AppBundle:AppBundle", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/AnimationUI/Info.plist b/submodules/AnimationUI/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/AnimationUI/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/AppBundle/BUCK b/submodules/AppBundle/BUCK index 7c15ea5d15..34e2293199 100644 --- a/submodules/AppBundle/BUCK +++ b/submodules/AppBundle/BUCK @@ -3,14 +3,13 @@ load("//Config:buck_rule_macros.bzl", "static_library") static_library( name = "AppBundle", srcs = glob([ - "Sources/**/*.swift", "Sources/**/*.m", ]), headers = glob([ "Sources/**/*.h", ]), exported_headers = glob([ - "Sources/**/*.h", + "PublicHeaders/**/*.h", ]), deps = [ ], diff --git a/submodules/AppBundle/BUILD b/submodules/AppBundle/BUILD new file mode 100644 index 0000000000..92d153bd64 --- /dev/null +++ b/submodules/AppBundle/BUILD @@ -0,0 +1,23 @@ + +objc_library( + name = "AppBundle", + module_name = "AppBundle", + enable_modules = True, + srcs = glob([ + "Sources/**/*.m", + "Sources/**/*.h", + ]), + hdrs = glob([ + "PublicHeaders/**/*.h", + ]), + includes = [ + "PublicHeaders", + ], + sdk_frameworks = [ + "Foundation", + "UIKit", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/AppBundle/Info.plist b/submodules/AppBundle/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/AppBundle/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/AppBundle/Sources/AppBundle.h b/submodules/AppBundle/PublicHeaders/AppBundle/AppBundle.h similarity index 53% rename from submodules/AppBundle/Sources/AppBundle.h rename to submodules/AppBundle/PublicHeaders/AppBundle/AppBundle.h index b78e84bc8d..d00fe35efa 100644 --- a/submodules/AppBundle/Sources/AppBundle.h +++ b/submodules/AppBundle/PublicHeaders/AppBundle/AppBundle.h @@ -1,12 +1,6 @@ #import #import -//! Project version number for AppBundle. -FOUNDATION_EXPORT double AppBundleVersionNumber; - -//! Project version string for AppBundle. -FOUNDATION_EXPORT const unsigned char AppBundleVersionString[]; - NSBundle * _Nonnull getAppBundle(void); @interface UIImage (AppBundle) diff --git a/submodules/AppBundle/Sources/AppBundle.m b/submodules/AppBundle/Sources/AppBundle/AppBundle.m similarity index 96% rename from submodules/AppBundle/Sources/AppBundle.m rename to submodules/AppBundle/Sources/AppBundle/AppBundle.m index 8103a03e40..66f5e08fc8 100644 --- a/submodules/AppBundle/Sources/AppBundle.m +++ b/submodules/AppBundle/Sources/AppBundle/AppBundle.m @@ -1,4 +1,4 @@ -#import "AppBundle.h" +#import NSBundle * _Nonnull getAppBundle() { NSBundle *bundle = [NSBundle mainBundle]; diff --git a/submodules/AppLock/BUILD b/submodules/AppLock/BUILD new file mode 100644 index 0000000000..ae89bbf200 --- /dev/null +++ b/submodules/AppLock/BUILD @@ -0,0 +1,25 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "AppLock", + module_name = "AppLock", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/Postbox:Postbox", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/MonotonicTime:MonotonicTime", + "//submodules/PasscodeUI:PasscodeUI", + "//submodules/TelegramUIPreferences:TelegramUIPreferences", + "//submodules/ImageBlur:ImageBlur", + "//submodules/AccountContext:AccountContext", + "//submodules/AppLockState:AppLockState", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/AppLock/Sources/AppLock.swift b/submodules/AppLock/Sources/AppLock.swift index e339ed19ad..112873aac4 100644 --- a/submodules/AppLock/Sources/AppLock.swift +++ b/submodules/AppLock/Sources/AppLock.swift @@ -9,6 +9,7 @@ import TelegramPresentationData import PasscodeUI import TelegramUIPreferences import ImageBlur +import FastBlur import AppLockState private func isLocked(passcodeSettings: PresentationPasscodeSettings, state: LockState, isApplicationActive: Bool) -> Bool { diff --git a/submodules/AppLockState/BUILD b/submodules/AppLockState/BUILD new file mode 100644 index 0000000000..d87c6ecb7f --- /dev/null +++ b/submodules/AppLockState/BUILD @@ -0,0 +1,15 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "AppLockState", + module_name = "AppLockState", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/MonotonicTime:MonotonicTime", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/ArchivedStickerPacksNotice/BUILD b/submodules/ArchivedStickerPacksNotice/BUILD new file mode 100644 index 0000000000..296ed10c8d --- /dev/null +++ b/submodules/ArchivedStickerPacksNotice/BUILD @@ -0,0 +1,29 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ArchivedStickerPacksNotice", + module_name = "ArchivedStickerPacksNotice", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/SyncCore:SyncCore", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/AccountContext:AccountContext", + "//submodules/TelegramUIPreferences:TelegramUIPreferences", + "//submodules/StickerResources:StickerResources", + "//submodules/AlertUI:AlertUI", + "//submodules/PresentationDataUtils:PresentationDataUtils", + "//submodules/MergeLists:MergeLists", + "//submodules/ItemListUI:ItemListUI", + "//submodules/ItemListStickerPackItem:ItemListStickerPackItem", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit.xcworkspace/contents.xcworkspacedata b/submodules/AsyncDisplayKit/AsyncDisplayKit.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 574f0ec195..0000000000 --- a/submodules/AsyncDisplayKit/AsyncDisplayKit.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/submodules/AsyncDisplayKit/AsyncDisplayKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003d..0000000000 --- a/submodules/AsyncDisplayKit/AsyncDisplayKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/submodules/AsyncDisplayKit/AsyncDisplayKit.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index 08de0be8d3..0000000000 --- a/submodules/AsyncDisplayKit/AsyncDisplayKit.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded - - - diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/ASPagerFlowLayout.m b/submodules/AsyncDisplayKit/AsyncDisplayKit/ASPagerFlowLayout.m deleted file mode 100644 index df8ce69868..0000000000 --- a/submodules/AsyncDisplayKit/AsyncDisplayKit/ASPagerFlowLayout.m +++ /dev/null @@ -1,96 +0,0 @@ -// -// ASPagerFlowLayout.m -// AsyncDisplayKit -// -// Created by Levi McCallum on 2/12/16. -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#ifndef MINIMAL_ASDK -#import - -@interface ASPagerFlowLayout () { - BOOL _didRotate; - CGRect _cachedCollectionViewBounds; - NSIndexPath *_currentIndexPath; -} - -@end - -@implementation ASPagerFlowLayout - -- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity -{ - NSInteger currentPage = ceil(proposedContentOffset.x / self.collectionView.bounds.size.width); - _currentIndexPath = [NSIndexPath indexPathForItem:currentPage inSection:0]; - - return [super targetContentOffsetForProposedContentOffset:proposedContentOffset withScrollingVelocity:velocity]; -} - - -- (void)prepareForAnimatedBoundsChange:(CGRect)oldBounds -{ - // Cache the current page if a rotation did happen. This happens before the rotation animation - // is occuring and the bounds changed so we use this as an opportunity to cache the current index path - if (_cachedCollectionViewBounds.size.width != self.collectionView.bounds.size.width) { - _cachedCollectionViewBounds = self.collectionView.bounds; - - // Figurring out current page based on the old bounds visible space - CGRect visibleRect = oldBounds; - - CGFloat visibleXCenter = CGRectGetMidX(visibleRect); - NSArray *layoutAttributes = [self layoutAttributesForElementsInRect:visibleRect]; - for (UICollectionViewLayoutAttributes *attributes in layoutAttributes) { - if ([attributes representedElementCategory] == UICollectionElementCategoryCell && attributes.center.x == visibleXCenter) { - _currentIndexPath = attributes.indexPath; - break; - } - } - - _didRotate = YES; - } - - [super prepareForAnimatedBoundsChange:oldBounds]; -} -- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset -{ - // Don't mess around if the user is interacting with the page node. Although if just a rotation happened we should - // try to use the current index path to not end up setting the target content offset to something in between pages - if (_didRotate || (!self.collectionView.isDecelerating && !self.collectionView.isTracking)) { - _didRotate = NO; - if (_currentIndexPath) { - return [self _targetContentOffsetForItemAtIndexPath:_currentIndexPath proposedContentOffset:proposedContentOffset]; - } - } - - return [super targetContentOffsetForProposedContentOffset:proposedContentOffset]; -} - -- (CGPoint)_targetContentOffsetForItemAtIndexPath:(NSIndexPath *)indexPath proposedContentOffset:(CGPoint)proposedContentOffset -{ - if ([self _dataSourceIsEmpty]) { - return proposedContentOffset; - } - - UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:_currentIndexPath]; - if (attributes == nil) { - return proposedContentOffset; - } - - CGFloat xOffset = (CGRectGetWidth(self.collectionView.bounds) - CGRectGetWidth(attributes.frame)) / 2.0; - return CGPointMake(attributes.frame.origin.x - xOffset, proposedContentOffset.y); -} - -- (BOOL)_dataSourceIsEmpty -{ - return ([self.collectionView numberOfSections] == 0 || - [self.collectionView numberOfItemsInSection:0] == 0); -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.h b/submodules/AsyncDisplayKit/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.h deleted file mode 100644 index 8e0602911e..0000000000 --- a/submodules/AsyncDisplayKit/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// ASLayoutElementInspectorCell.h -// AsyncDisplayKit -// -// Created by Hannah Troisi on 3/27/16. -// Copyright © 2016 Facebook. All rights reserved. -// - -#ifndef MINIMAL_ASDK - -#import - -typedef NS_ENUM(NSInteger, ASLayoutElementPropertyType) { - ASLayoutElementPropertyFlexGrow = 0, - ASLayoutElementPropertyFlexShrink, - ASLayoutElementPropertyAlignSelf, - ASLayoutElementPropertyFlexBasis, - ASLayoutElementPropertySpacingBefore, - ASLayoutElementPropertySpacingAfter, - ASLayoutElementPropertyAscender, - ASLayoutElementPropertyDescender, - ASLayoutElementPropertyCount -}; - -@interface ASLayoutElementInspectorCell : ASCellNode - -- (instancetype)initWithProperty:(ASLayoutElementPropertyType)property layoutElementToEdit:(id)layoutable NS_DESIGNATED_INITIALIZER; - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.m b/submodules/AsyncDisplayKit/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.m deleted file mode 100644 index 7e5ca1ad9c..0000000000 --- a/submodules/AsyncDisplayKit/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.m +++ /dev/null @@ -1,570 +0,0 @@ -// -// ASLayoutElementInspectorCell.m -// AsyncDisplayKit -// -// Created by Hannah Troisi on 3/27/16. -// Copyright © 2016 Facebook. All rights reserved. -// -#ifndef MINIMAL_ASDK -#import -#import - -typedef NS_ENUM(NSInteger, CellDataType) { - CellDataTypeBool, - CellDataTypeFloat, -}; - -__weak static ASLayoutElementInspectorCell *__currentlyOpenedCell = nil; - -@protocol InspectorCellEditingBubbleProtocol -- (void)valueChangedToIndex:(NSUInteger)index; -@end - -@interface ASLayoutElementInspectorCellEditingBubble : ASDisplayNode -@property (nonatomic, strong, readwrite) id delegate; -- (instancetype)initWithEnumOptions:(BOOL)yes enumStrings:(NSArray *)options currentOptionIndex:(NSUInteger)currentOption; -- (instancetype)initWithSliderMinValue:(CGFloat)min maxValue:(CGFloat)max currentValue:(CGFloat)current -;@end - -@interface ASLayoutElementInspectorCell () -@end - -@implementation ASLayoutElementInspectorCell -{ - ASLayoutElementPropertyType _propertyType; - CellDataType _dataType; - id _layoutElementToEdit; - - ASButtonNode *_buttonNode; - ASTextNode *_textNode; - ASTextNode *_textNode2; - - ASLayoutElementInspectorCellEditingBubble *_textBubble; -} - -#pragma mark - Lifecycle - -- (instancetype)initWithProperty:(ASLayoutElementPropertyType)property layoutElementToEdit:(id)layoutElement -{ - self = [super init]; - if (self) { - - _propertyType = property; - _dataType = [ASLayoutElementInspectorCell dataTypeForProperty:property]; - _layoutElementToEdit = layoutElement; - - self.automaticallyManagesSubnodes = YES; - - _buttonNode = [self makeBtnNodeWithTitle:[ASLayoutElementInspectorCell propertyStringForPropertyType:property]]; - [_buttonNode addTarget:self action:@selector(buttonTapped:) forControlEvents:ASControlNodeEventTouchUpInside]; - - _textNode = [[ASTextNode alloc] init]; - _textNode.attributedText = [ASLayoutElementInspectorCell propertyValueAttributedStringForProperty:property withLayoutElement:layoutElement]; - - [self updateButtonStateForProperty:property withLayoutElement:layoutElement]; - - _textNode2 = [[ASTextNode alloc] init]; - _textNode2.attributedText = [ASLayoutElementInspectorCell propertyValueDetailAttributedStringForProperty:property withLayoutElement:layoutElement]; - - } - return self; -} - -- (void)updateButtonStateForProperty:(ASLayoutElementPropertyType)property withLayoutElement:(id)layoutElement -{ - if (property == ASLayoutElementPropertyFlexGrow) { - _buttonNode.selected = layoutElement.style.flexGrow; - } - else if (property == ASLayoutElementPropertyFlexShrink) { - _buttonNode.selected = layoutElement.style.flexShrink; - } -} - -- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize -{ - ASStackLayoutSpec *horizontalSpec = [ASStackLayoutSpec horizontalStackLayoutSpec]; - horizontalSpec.children = @[_buttonNode, _textNode]; - horizontalSpec.style.flexGrow = 1.0; - horizontalSpec.alignItems = ASStackLayoutAlignItemsCenter; - horizontalSpec.justifyContent = ASStackLayoutJustifyContentSpaceBetween; - - ASLayoutSpec *childSpec; - if (_textBubble) { - ASStackLayoutSpec *verticalSpec = [ASStackLayoutSpec verticalStackLayoutSpec]; - verticalSpec.children = @[horizontalSpec, _textBubble]; - verticalSpec.spacing = 8; - verticalSpec.style.flexGrow = 1.0; - _textBubble.style.flexGrow = 1.0; - childSpec = verticalSpec; - } else { - childSpec = horizontalSpec; - } - ASInsetLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(2, 4, 2, 4) child:childSpec]; - insetSpec.style.flexGrow =1.0; - - return insetSpec; -} - -+ (NSAttributedString *)propertyValueAttributedStringForProperty:(ASLayoutElementPropertyType)property withLayoutElement:(id)layoutElement -{ - NSString *valueString; - - switch (property) { - case ASLayoutElementPropertyFlexGrow: - valueString = layoutElement.style.flexGrow ? @"YES" : @"NO"; - break; - case ASLayoutElementPropertyFlexShrink: - valueString = layoutElement.style.flexShrink ? @"YES" : @"NO"; - break; - case ASLayoutElementPropertyAlignSelf: - valueString = [ASLayoutElementInspectorCell alignSelfEnumValueString:layoutElement.style.alignSelf]; - break; - case ASLayoutElementPropertyFlexBasis: - if (layoutElement.style.flexBasis.unit && layoutElement.style.flexBasis.value) { // ENUM TYPE - valueString = [NSString stringWithFormat:@"%0.0f %@", layoutElement.style.flexBasis.value, - [ASLayoutElementInspectorCell ASRelativeDimensionEnumString:layoutElement.style.alignSelf]]; - } else { - valueString = @"0 pts"; - } - break; - case ASLayoutElementPropertySpacingBefore: - valueString = [NSString stringWithFormat:@"%0.0f", layoutElement.style.spacingBefore]; - break; - case ASLayoutElementPropertySpacingAfter: - valueString = [NSString stringWithFormat:@"%0.0f", layoutElement.style.spacingAfter]; - break; - case ASLayoutElementPropertyAscender: - valueString = [NSString stringWithFormat:@"%0.0f", layoutElement.style.ascender]; - break; - case ASLayoutElementPropertyDescender: - valueString = [NSString stringWithFormat:@"%0.0f", layoutElement.style.descender]; - break; - default: - valueString = @"?"; - break; - } - return [ASLayoutElementInspectorCell attributedStringFromString:valueString]; -} - -+ (NSAttributedString *)propertyValueDetailAttributedStringForProperty:(ASLayoutElementPropertyType)property withLayoutElement:(id)layoutElement -{ - NSString *valueString; - - switch (property) { - case ASLayoutElementPropertyFlexGrow: - case ASLayoutElementPropertyFlexShrink: - case ASLayoutElementPropertyAlignSelf: - case ASLayoutElementPropertyFlexBasis: - case ASLayoutElementPropertySpacingBefore: - case ASLayoutElementPropertySpacingAfter: - case ASLayoutElementPropertyAscender: - case ASLayoutElementPropertyDescender: - default: - return nil; - } - return [ASLayoutElementInspectorCell attributedStringFromString:valueString]; -} - -- (void)endEditingValue -{ - _textBubble = nil; - __currentlyOpenedCell = nil; - _buttonNode.selected = NO; - [self setNeedsLayout]; -} - -- (void)beginEditingValue -{ - _textBubble.delegate = self; - __currentlyOpenedCell = self; - [self setNeedsLayout]; -} - -- (void)valueChangedToIndex:(NSUInteger)index -{ - switch (_propertyType) { - - case ASLayoutElementPropertyAlignSelf: - _layoutElementToEdit.style.alignSelf = (ASStackLayoutAlignSelf)index; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[ASLayoutElementInspectorCell alignSelfEnumValueString:index]]; - break; - - case ASLayoutElementPropertySpacingBefore: - _layoutElementToEdit.style.spacingBefore = (CGFloat)index; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.spacingBefore]]; - break; - - case ASLayoutElementPropertySpacingAfter: - _layoutElementToEdit.style.spacingAfter = (CGFloat)index; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.spacingAfter]]; - break; - - case ASLayoutElementPropertyAscender: - _layoutElementToEdit.style.ascender = (CGFloat)index; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.ascender]]; - break; - - case ASLayoutElementPropertyDescender: - _layoutElementToEdit.style.descender = (CGFloat)index; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.descender]]; - break; - - default: - break; - } - - [self setNeedsLayout]; -} - -#pragma mark - gesture handling - -- (void)buttonTapped:(ASButtonNode *)sender -{ - BOOL selfIsEditing = (self == __currentlyOpenedCell); - [__currentlyOpenedCell endEditingValue]; - if (selfIsEditing) { - sender.selected = NO; - return; - } - -// NSUInteger currentAlignSelfValue; -// NSUInteger nextAlignSelfValue; -// CGFloat newValue; - - sender.selected = !sender.selected; - switch (_propertyType) { - - case ASLayoutElementPropertyFlexGrow: - _layoutElementToEdit.style.flexGrow = sender.isSelected ? 1.0 : 0.0; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:sender.selected ? @"YES" : @"NO"]; - break; - - case ASLayoutElementPropertyFlexShrink: - _layoutElementToEdit.style.flexShrink = sender.isSelected ? 1.0 : 0.0; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:sender.selected ? @"YES" : @"NO"]; - break; - - case ASLayoutElementPropertyAlignSelf: - _textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithEnumOptions:YES - enumStrings:[ASLayoutElementInspectorCell alignSelfEnumStringArray] - currentOptionIndex:_layoutElementToEdit.style.alignSelf]; - - [self beginEditingValue]; -// if ([self layoutSpec]) { -// currentAlignSelfValue = [[self layoutSpec] alignSelf]; -// nextAlignSelfValue = (currentAlignSelfValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignSelfValue + 1 : 0; -// [[self layoutSpec] setAlignSelf:nextAlignSelfValue]; -// -// } else if ([self node]) { -// currentAlignSelfValue = [[self node] alignSelf]; -// nextAlignSelfValue = (currentAlignSelfValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignSelfValue + 1 : 0; -// [[self node] setAlignSelf:nextAlignSelfValue]; -// } - break; - - case ASLayoutElementPropertySpacingBefore: - _textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithSliderMinValue:0 maxValue:100 currentValue:_layoutElementToEdit.style.spacingBefore]; - [self beginEditingValue]; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.spacingBefore]]; - break; - - case ASLayoutElementPropertySpacingAfter: - _textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithSliderMinValue:0 maxValue:100 currentValue:_layoutElementToEdit.style.spacingAfter]; - [self beginEditingValue]; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.spacingAfter]]; - break; - - - case ASLayoutElementPropertyAscender: - _textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithSliderMinValue:0 maxValue:100 currentValue:_layoutElementToEdit.style.ascender]; - [self beginEditingValue]; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.ascender]]; - break; - - case ASLayoutElementPropertyDescender: - _textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithSliderMinValue:0 maxValue:100 currentValue:_layoutElementToEdit.style.descender]; - [self beginEditingValue]; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.descender]]; - break; - - default: - break; - } - [self setNeedsLayout]; -} - -#pragma mark - cast layoutElementToEdit - -- (ASDisplayNode *)node -{ - if (_layoutElementToEdit.layoutElementType == ASLayoutElementTypeDisplayNode) { - return (ASDisplayNode *)_layoutElementToEdit; - } - return nil; -} - -- (ASLayoutSpec *)layoutSpec -{ - if (_layoutElementToEdit.layoutElementType == ASLayoutElementTypeLayoutSpec) { - return (ASLayoutSpec *)_layoutElementToEdit; - } - return nil; -} - -#pragma mark - data / property type helper methods - -+ (CellDataType)dataTypeForProperty:(ASLayoutElementPropertyType)property -{ - switch (property) { - - case ASLayoutElementPropertyFlexGrow: - case ASLayoutElementPropertyFlexShrink: - return CellDataTypeBool; - - case ASLayoutElementPropertySpacingBefore: - case ASLayoutElementPropertySpacingAfter: - case ASLayoutElementPropertyAscender: - case ASLayoutElementPropertyDescender: - return CellDataTypeFloat; - - default: - break; - } - return CellDataTypeBool; -} - -+ (NSString *)propertyStringForPropertyType:(ASLayoutElementPropertyType)property -{ - NSString *string; - switch (property) { - case ASLayoutElementPropertyFlexGrow: - string = @"FlexGrow"; - break; - case ASLayoutElementPropertyFlexShrink: - string = @"FlexShrink"; - break; - case ASLayoutElementPropertyAlignSelf: - string = @"AlignSelf"; - break; - case ASLayoutElementPropertyFlexBasis: - string = @"FlexBasis"; - break; - case ASLayoutElementPropertySpacingBefore: - string = @"SpacingBefore"; - break; - case ASLayoutElementPropertySpacingAfter: - string = @"SpacingAfter"; - break; - case ASLayoutElementPropertyAscender: - string = @"Ascender"; - break; - case ASLayoutElementPropertyDescender: - string = @"Descender"; - break; - default: - string = @"Unknown"; - break; - } - return string; -} - -+ (NSDictionary *)alignSelfTypeNames -{ - return @{@(ASStackLayoutAlignSelfAuto) : @"Auto", - @(ASStackLayoutAlignSelfStart) : @"Start", - @(ASStackLayoutAlignSelfEnd) : @"End", - @(ASStackLayoutAlignSelfCenter) : @"Center", - @(ASStackLayoutAlignSelfStretch) : @"Stretch"}; -} - -+ (NSString *)alignSelfEnumValueString:(NSUInteger)type -{ - return [[self class] alignSelfTypeNames][@(type)]; -} - -+ (NSArray *)alignSelfEnumStringArray -{ - return @[@"ASStackLayoutAlignSelfAuto", - @"ASStackLayoutAlignSelfStart", - @"ASStackLayoutAlignSelfEnd", - @"ASStackLayoutAlignSelfCenter", - @"ASStackLayoutAlignSelfStretch"]; -} - -+ (NSDictionary *)ASRelativeDimensionTypeNames -{ - return @{@(ASDimensionUnitPoints) : @"pts", - @(ASDimensionUnitFraction) : @"%"}; -} - -+ (NSString *)ASRelativeDimensionEnumString:(NSUInteger)type -{ - return [[self class] ASRelativeDimensionTypeNames][@(type)]; -} - -#pragma mark - formatting helper methods - -+ (NSAttributedString *)attributedStringFromString:(NSString *)string -{ - return [ASLayoutElementInspectorCell attributedStringFromString:string withTextColor:[UIColor whiteColor]]; -} - -+ (NSAttributedString *)attributedStringFromString:(NSString *)string withTextColor:(nullable UIColor *)color -{ - NSDictionary *attributes = @{NSForegroundColorAttributeName : color, - NSFontAttributeName : [UIFont fontWithName:@"Menlo-Regular" size:12]}; - - return [[NSAttributedString alloc] initWithString:string attributes:attributes]; -} - -- (ASButtonNode *)makeBtnNodeWithTitle:(NSString *)title -{ - UIColor *orangeColor = [UIColor colorWithRed:255/255.0 green:181/255.0 blue:68/255.0 alpha:1]; - UIImage *orangeStretchBtnImg = [ASLayoutElementInspectorCell imageForButtonWithBackgroundColor:orangeColor - borderColor:[UIColor whiteColor] - borderWidth:3]; - UIImage *greyStretchBtnImg = [ASLayoutElementInspectorCell imageForButtonWithBackgroundColor:[UIColor darkGrayColor] - borderColor:[UIColor lightGrayColor] - borderWidth:3]; - UIImage *clearStretchBtnImg = [ASLayoutElementInspectorCell imageForButtonWithBackgroundColor:[UIColor clearColor] - borderColor:[UIColor whiteColor] - borderWidth:3]; - ASButtonNode *btn = [[ASButtonNode alloc] init]; - btn.contentEdgeInsets = UIEdgeInsetsMake(5, 5, 5, 5); - [btn setAttributedTitle:[ASLayoutElementInspectorCell attributedStringFromString:title] forState:ASControlStateNormal]; - [btn setAttributedTitle:[ASLayoutElementInspectorCell attributedStringFromString:title withTextColor:[UIColor lightGrayColor]] forState:ASControlStateDisabled]; - [btn setBackgroundImage:clearStretchBtnImg forState:ASControlStateNormal]; - [btn setBackgroundImage:orangeStretchBtnImg forState:ASControlStateSelected]; - [btn setBackgroundImage:greyStretchBtnImg forState:ASControlStateDisabled]; - - return btn; -} - -#define CORNER_RADIUS 3 -+ (UIImage *)imageForButtonWithBackgroundColor:(UIColor *)backgroundColor borderColor:(UIColor *)borderColor borderWidth:(CGFloat)width -{ - CGSize unstretchedSize = CGSizeMake(2 * CORNER_RADIUS + 1, 2 * CORNER_RADIUS + 1); - CGRect rect = (CGRect) {CGPointZero, unstretchedSize}; - UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:CORNER_RADIUS]; - - // create a graphics context for the following status button - UIGraphicsBeginImageContextWithOptions(unstretchedSize, NO, 0); - - [path addClip]; - [backgroundColor setFill]; - [path fill]; - - path.lineWidth = width; - [borderColor setStroke]; - [path stroke]; - - UIImage *btnImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - - return [btnImage stretchableImageWithLeftCapWidth:CORNER_RADIUS topCapHeight:CORNER_RADIUS]; -} - -@end - - - -@implementation ASLayoutElementInspectorCellEditingBubble -{ - NSMutableArray *_textNodes; - ASDisplayNode *_slider; -} - -- (instancetype)initWithEnumOptions:(BOOL)yes enumStrings:(NSArray *)options currentOptionIndex:(NSUInteger)currentOption -{ - self = [super init]; - if (self) { - self.automaticallyManagesSubnodes = YES; - self.backgroundColor = [UIColor colorWithRed:255/255.0 green:181/255.0 blue:68/255.0 alpha:1]; - - _textNodes = [[NSMutableArray alloc] init]; - int index = 0; - for (NSString *optionStr in options) { - ASButtonNode *btn = [[ASButtonNode alloc] init]; - [btn setAttributedTitle:[ASLayoutElementInspectorCell attributedStringFromString:optionStr] forState:ASControlStateNormal]; - [btn setAttributedTitle:[ASLayoutElementInspectorCell attributedStringFromString:optionStr withTextColor:[UIColor redColor]] - forState:ASControlStateSelected]; - [btn addTarget:self action:@selector(enumOptionSelected:) forControlEvents:ASControlNodeEventTouchUpInside]; - btn.selected = (index == currentOption) ? YES : NO; - [_textNodes addObject:btn]; - index++; - } - } - return self; -} - -- (instancetype)initWithSliderMinValue:(CGFloat)min maxValue:(CGFloat)max currentValue:(CGFloat)current -{ - if (self = [super init]) { - self.userInteractionEnabled = YES; - self.automaticallyManagesSubnodes = YES; - self.backgroundColor = [UIColor colorWithRed:255/255.0 green:181/255.0 blue:68/255.0 alpha:1]; - - __weak id weakSelf = self; - _slider = [[ASDisplayNode alloc] initWithViewBlock:^UIView * _Nonnull{ - UISlider *slider = [[UISlider alloc] init]; - slider.minimumValue = min; - slider.maximumValue = max; - slider.value = current; - [slider addTarget:weakSelf action:@selector(sliderValueChanged:) forControlEvents:UIControlEventValueChanged]; - - return slider; - }]; - _slider.userInteractionEnabled = YES; - } - return self; -} - -- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize -{ - _slider.style.preferredSize = CGSizeMake(constrainedSize.max.width, 25); - - NSMutableArray *children = [[NSMutableArray alloc] init]; - if (_textNodes) { - ASStackLayoutSpec *textStack = [ASStackLayoutSpec verticalStackLayoutSpec]; - textStack.children = _textNodes; - textStack.spacing = 2; - [children addObject:textStack]; - } - if (_slider) { - _slider.style.flexGrow = 1.0; - [children addObject:_slider]; - } - - ASStackLayoutSpec *verticalStackSpec = [ASStackLayoutSpec verticalStackLayoutSpec]; - verticalStackSpec.children = children; - verticalStackSpec.spacing = 2; - verticalStackSpec.style.flexGrow = 1.0; - verticalStackSpec.style.alignSelf = ASStackLayoutAlignSelfStretch; - - ASInsetLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(8, 8, 8, 8) child:verticalStackSpec]; - - return insetSpec; -} - -#pragma mark - gesture handling -- (void)enumOptionSelected:(ASButtonNode *)sender -{ - sender.selected = !sender.selected; - for (ASButtonNode *node in _textNodes) { - if (node != sender) { - node.selected = NO; - } - } - [self.delegate valueChangedToIndex:[_textNodes indexOfObject:sender]]; - [self setNeedsLayout]; -} - -- (void)sliderValueChanged:(UISlider *)sender -{ - [self.delegate valueChangedToIndex:roundf(sender.value)]; -} - -@end - - -#endif diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Debug/ASLayoutElementInspectorNode.m b/submodules/AsyncDisplayKit/AsyncDisplayKit/Debug/ASLayoutElementInspectorNode.m deleted file mode 100644 index b71c2f7413..0000000000 --- a/submodules/AsyncDisplayKit/AsyncDisplayKit/Debug/ASLayoutElementInspectorNode.m +++ /dev/null @@ -1,426 +0,0 @@ -// -// ASLayoutElementInspectorNode.m -// Sample -// -// Created by Hannah Troisi on 3/19/16. -// Copyright © 2016 Facebook. All rights reserved. -// - -#import -#ifndef MINIMAL_ASDK -#import -#endif -#import -#import -#import -#import -#import - -@interface ASLayoutElementInspectorNode () -#ifndef MINIMAL_ASDK - -#endif -@end - -@implementation ASLayoutElementInspectorNode -{ -#ifndef MINIMAL_ASDK - ASTableNode *_tableNode; -#endif -} - -#pragma mark - class methods -+ (instancetype)sharedInstance -{ - static ASLayoutElementInspectorNode *__inspector = nil; - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - __inspector = [[ASLayoutElementInspectorNode alloc] init]; - }); - - return __inspector; -} - -#pragma mark - lifecycle -- (instancetype)init -{ - self = [super init]; - if (self) { - -#ifndef MINIMAL_ASDK - _tableNode = [[ASTableNode alloc] init]; - _tableNode.delegate = self; - _tableNode.dataSource = self; - - [self addSubnode:_tableNode]; // required because of manual layout -#endif - } - return self; -} - -- (void)didLoad -{ - [super didLoad]; -#ifndef MINIMAL_ASDK - _tableNode.view.backgroundColor = [UIColor colorWithRed:40/255.0 green:43/255.0 blue:53/255.0 alpha:1]; - _tableNode.view.separatorStyle = UITableViewCellSeparatorStyleNone; - _tableNode.view.allowsSelection = NO; - _tableNode.view.sectionHeaderHeight = 40; -#endif -} - -- (void)layout -{ - [super layout]; -#ifndef MINIMAL_ASDK - _tableNode.frame = self.bounds; -#endif -} - -#pragma mark - intstance methods -- (void)setLayoutElementToEdit:(id)layoutElementToEdit -{ - if (_layoutElementToEdit != layoutElementToEdit) { - _layoutElementToEdit = layoutElementToEdit; - } -#ifndef MINIMAL_ASDK - [_tableNode reloadData]; -#endif -} - -#pragma mark - ASTableDataSource - -#ifndef MINIMAL_ASDK -- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath -{ - if (indexPath.section == 0) { - NSDictionary *attributes = @{NSForegroundColorAttributeName : [UIColor colorWithRed:255/255.0 green:181/255.0 blue:68/255.0 alpha:1], - NSFontAttributeName : [UIFont fontWithName:@"Menlo-Regular" size:12]}; - ASTextCellNode *textCell = [[ASTextCellNode alloc] initWithAttributes:attributes insets:UIEdgeInsetsMake(0, 4, 0, 0)]; - textCell.text = [_layoutElementToEdit description]; - return textCell; - } else { - return [[ASLayoutElementInspectorCell alloc] initWithProperty:(ASLayoutElementPropertyType)indexPath.row layoutElementToEdit:_layoutElementToEdit]; - } -} - -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section -{ - if (section == 0) { - return 1; - } else { - return ASLayoutElementPropertyCount; - } -} - -- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView -{ - return 2; -} - -- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section -{ - UILabel *headerTitle = [[UILabel alloc] initWithFrame:CGRectZero]; - - NSString *title; - if (section == 0) { - title = @" Item"; - } else { - title = @" Properties"; - } - - NSDictionary *attributes = @{NSForegroundColorAttributeName : [UIColor whiteColor], - NSFontAttributeName : [UIFont fontWithName:@"Menlo-Bold" size:12]}; - headerTitle.attributedText = [[NSAttributedString alloc] initWithString:title attributes:attributes]; - - return headerTitle; -} - -#endif - -//- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize -//{ -// // navigate layout hierarchy -// -// _parentNodeNavBtn.alignSelf = ASStackLayoutAlignSelfCenter; -// _childNodeNavBtn.alignSelf = ASStackLayoutAlignSelfCenter; -// -// ASStackLayoutSpec *horizontalStackNav = [ASStackLayoutSpec horizontalStackLayoutSpec]; -// horizontalStackNav.style.flexGrow = 1.0; -// horizontalStackNav.alignSelf = ASStackLayoutAlignSelfCenter; -// horizontalStackNav.children = @[_siblingNodeLefttNavBtn, _siblingNodeRightNavBtn]; -// -// ASStackLayoutSpec *horizontalStack = [ASStackLayoutSpec horizontalStackLayoutSpec]; -// horizontalStack.style.flexGrow = 1.0; -// ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; -// -// spacer.style.flexGrow = 1.0; -// horizontalStack.children = @[_flexGrowBtn, spacer]; -// _flexGrowValue.alignSelf = ASStackLayoutAlignSelfEnd; // FIXME: make framework give a warning if you use ASAlignmentBottom!!!!! -// -// ASStackLayoutSpec *horizontalStack2 = [ASStackLayoutSpec horizontalStackLayoutSpec]; -// horizontalStack2.style.flexGrow = 1.0; -// horizontalStack2.children = @[_flexShrinkBtn, spacer]; -// _flexShrinkValue.alignSelf = ASStackLayoutAlignSelfEnd; -// -// ASStackLayoutSpec *horizontalStack3 = [ASStackLayoutSpec horizontalStackLayoutSpec]; -// horizontalStack3.style.flexGrow = 1.0; -// horizontalStack3.children = @[_flexBasisBtn, spacer, _flexBasisValue]; -// _flexBasisValue.alignSelf = ASStackLayoutAlignSelfEnd; -// -// ASStackLayoutSpec *itemDescriptionStack = [ASStackLayoutSpec verticalStackLayoutSpec]; -// itemDescriptionStack.children = @[_itemDescription]; -// itemDescriptionStack.spacing = 5; -// itemDescriptionStack.style.flexGrow = 1.0; -// -// ASStackLayoutSpec *layoutableStack = [ASStackLayoutSpec verticalStackLayoutSpec]; -// layoutableStack.children = @[_layoutablePropertiesSectionTitle, horizontalStack, horizontalStack2, horizontalStack3, _alignSelfBtn]; -// layoutableStack.spacing = 5; -// layoutableStack.style.flexGrow = 1.0; -// -// ASStackLayoutSpec *layoutSpecStack = [ASStackLayoutSpec verticalStackLayoutSpec]; -// layoutSpecStack.children = @[_layoutSpecPropertiesSectionTitle, _alignItemsBtn]; -// layoutSpecStack.spacing = 5; -// layoutSpecStack.style.flexGrow = 1.0; -// -// ASStackLayoutSpec *debugHelpStack = [ASStackLayoutSpec verticalStackLayoutSpec]; -// debugHelpStack.children = @[_debugSectionTitle, _vizNodeInsetSizeBtn, _vizNodeBordersBtn]; -// debugHelpStack.spacing = 5; -// debugHelpStack.style.flexGrow = 1.0; -// -// ASStackLayoutSpec *verticalLayoutableStack = [ASStackLayoutSpec verticalStackLayoutSpec]; -// verticalLayoutableStack.style.flexGrow = 1.0; -// verticalLayoutableStack.spacing = 20; -// verticalLayoutableStack.children = @[_parentNodeNavBtn, horizontalStackNav, _childNodeNavBtn, itemDescriptionStack, layoutableStack, layoutSpecStack, debugHelpStack]; -// verticalLayoutableStack.alignItems = ASStackLayoutAlignItemsStretch; // stretch headerStack to fill horizontal space -// -// ASLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(100, 10, 10, 10) child:verticalLayoutableStack]; -// insetSpec.style.flexGrow = 1.0; -// return insetSpec; -//} -// -//#pragma mark - configure Inspector node for layoutable -//- (void)updateInspectorWithLayoutable -//{ -// _itemDescription.attributedText = [self attributedStringFromLayoutable:_layoutElementToEdit]; -// -// if ([self node]) { -// UIColor *nodeBackgroundColor = [[self node] backgroundColor]; -// UIImage *colorBtnImg = [ASLayoutElementInspectorNode imageForButtonWithBackgroundColor:nodeBackgroundColor -// borderColor:[UIColor whiteColor] -// borderWidth:3]; -// [_itemBackgroundColorBtn setBackgroundImage:colorBtnImg forState:ASControlStateNormal]; -// } else { -// _itemBackgroundColorBtn.enabled = NO; -// } -// -// _flexGrowBtn.selected = [self.layoutElementToEdit flexGrow]; -// _flexGrowValue.attributedText = [self attributedStringFromString: (_flexGrowBtn.selected) ? @"YES" : @"NO"]; -// -// _flexShrinkBtn.selected = self.layoutElementToEdit.style.flexShrink; -// _flexShrinkValue.attributedText = [self attributedStringFromString: (_flexShrinkBtn.selected) ? @"YES" : @"NO"]; -// -// // _flexBasisBtn.selected = self.layoutElementToEdit.style.flexShrink; -// // _flexBasisValue.attributedText = [self attributedStringFromString: (_flexBasisBtn.selected) ? @"YES" : @"NO"]; -// -// -// NSUInteger alignSelfValue = [self.layoutElementToEdit alignSelf]; -// NSString *newTitle = [@"alignSelf:" stringByAppendingString:[self alignSelfName:alignSelfValue]]; -// [_alignSelfBtn setAttributedTitle:[self attributedStringFromString:newTitle] forState:ASControlStateNormal]; -// -// if ([self layoutSpec]) { -// _alignItemsBtn.enabled = YES; -//// NSUInteger alignItemsValue = [[self layoutSpec] alignItems]; -//// newTitle = [@"alignItems:" stringByAppendingString:[self alignSelfName:alignItemsValue]]; -//// [_alignItemsBtn setAttributedTitle:[self attributedStringFromString:newTitle] forState:ASControlStateNormal]; -// } -// -// [self setNeedsLayout]; -//} - - -//- (void)enableInspectorNodesForLayoutable -//{ -// if ([self layoutSpec]) { -// -// _itemBackgroundColorBtn.enabled = YES; -// _flexGrowBtn.enabled = YES; -// _flexShrinkBtn.enabled = YES; -// _flexBasisBtn.enabled = YES; -// _alignSelfBtn.enabled = YES; -// _spacingBeforeBtn.enabled = YES; -// _spacingAfterBtn.enabled = YES; -// _alignItemsBtn.enabled = YES; -// -// } else if ([self node]) { -// -// _itemBackgroundColorBtn.enabled = YES; -// _flexGrowBtn.enabled = YES; -// _flexShrinkBtn.enabled = YES; -// _flexBasisBtn.enabled = YES; -// _alignSelfBtn.enabled = YES; -// _spacingBeforeBtn.enabled = YES; -// _spacingAfterBtn.enabled = YES; -// _alignItemsBtn.enabled = NO; -// -// } else { -// -// _itemBackgroundColorBtn.enabled = NO; -// _flexGrowBtn.enabled = NO; -// _flexShrinkBtn.enabled = NO; -// _flexBasisBtn.enabled = NO; -// _alignSelfBtn.enabled = NO; -// _spacingBeforeBtn.enabled = NO; -// _spacingAfterBtn.enabled = NO; -// _alignItemsBtn.enabled = YES; -// } -//} - -//+ (NSDictionary *)alignSelfTypeNames -//{ -// return @{@(ASStackLayoutAlignSelfAuto) : @"Auto", -// @(ASStackLayoutAlignSelfStart) : @"Start", -// @(ASStackLayoutAlignSelfEnd) : @"End", -// @(ASStackLayoutAlignSelfCenter) : @"Center", -// @(ASStackLayoutAlignSelfStretch) : @"Stretch"}; -//} -// -//- (NSString *)alignSelfName:(NSUInteger)type -//{ -// return [[self class] alignSelfTypeNames][@(type)]; -//} -// -//+ (NSDictionary *)alignItemTypeNames -//{ -// return @{@(ASStackLayoutAlignItemsBaselineFirst) : @"BaselineFirst", -// @(ASStackLayoutAlignItemsBaselineLast) : @"BaselineLast", -// @(ASStackLayoutAlignItemsCenter) : @"Center", -// @(ASStackLayoutAlignItemsEnd) : @"End", -// @(ASStackLayoutAlignItemsStart) : @"Start", -// @(ASStackLayoutAlignItemsStretch) : @"Stretch"}; -//} -// -//- (NSString *)alignItemName:(NSUInteger)type -//{ -// return [[self class] alignItemTypeNames][@(type)]; -//} - -//#pragma mark - gesture handling -//- (void)changeColor:(ASButtonNode *)sender -//{ -// if ([self node]) { -// NSArray *colorArray = @[[UIColor orangeColor], -// [UIColor redColor], -// [UIColor greenColor], -// [UIColor purpleColor]]; -// -// UIColor *nodeBackgroundColor = [(ASDisplayNode *)self.layoutElementToEdit backgroundColor]; -// -// NSUInteger colorIndex = [colorArray indexOfObject:nodeBackgroundColor]; -// colorIndex = (colorIndex + 1 < [colorArray count]) ? colorIndex + 1 : 0; -// -// [[self node] setBackgroundColor: [colorArray objectAtIndex:colorIndex]]; -// } -// -// [self updateInspectorWithLayoutable]; -//} -// -//- (void)setFlexGrowValue:(ASButtonNode *)sender -//{ -// [sender setSelected:!sender.isSelected]; // FIXME: fix ASControlNode documentation that this is automatic - unlike highlighted, it is up to the application to decide when a button should be selected or not. Selected is a more persistant thing and highlighted is for the moment, like as a user has a finger on it, -// -// if ([self layoutSpec]) { -// [[self layoutSpec] setFlexGrow:sender.isSelected]; -// } else if ([self node]) { -// [[self node] setFlexGrow:sender.isSelected]; -// } -// -// [self updateInspectorWithLayoutable]; -//} -// -//- (void)setFlexShrinkValue:(ASButtonNode *)sender -//{ -// [sender setSelected:!sender.isSelected]; // FIXME: fix ASControlNode documentation that this is automatic - unlike highlighted, it is up to the application to decide when a button should be selected or not. Selected is a more persistant thing and highlighted is for the moment, like as a user has a finger on it, -// -// if ([self layoutSpec]) { -// [[self layoutSpec] setFlexShrink:sender.isSelected]; -// } else if ([self node]) { -// [[self node] setFlexShrink:sender.isSelected]; -// } -// -// [self updateInspectorWithLayoutable]; -//} -// -//- (void)setAlignSelfValue:(ASButtonNode *)sender -//{ -// NSUInteger currentAlignSelfValue; -// NSUInteger nextAlignSelfValue; -// -// if ([self layoutSpec]) { -// currentAlignSelfValue = [[self layoutSpec] alignSelf]; -// nextAlignSelfValue = (currentAlignSelfValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignSelfValue + 1 : 0; -// [[self layoutSpec] setAlignSelf:nextAlignSelfValue]; -// -// } else if ([self node]) { -// currentAlignSelfValue = [[self node] alignSelf]; -// nextAlignSelfValue = (currentAlignSelfValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignSelfValue + 1 : 0; -// [[self node] setAlignSelf:nextAlignSelfValue]; -// } -// -// [self updateInspectorWithLayoutable]; -//} -// -//- (void)setAlignItemsValue:(ASButtonNode *)sender -//{ -// NSUInteger currentAlignItemsValue; -// NSUInteger nextAlignItemsValue; -// -// if ([self layoutSpec]) { -// currentAlignItemsValue = [[self layoutSpec] alignSelf]; -// nextAlignItemsValue = (currentAlignItemsValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignItemsValue + 1 : 0; -//// [[self layoutSpec] setAlignItems:nextAlignItemsValue]; -// -// } else if ([self node]) { -// currentAlignItemsValue = [[self node] alignSelf]; -// nextAlignItemsValue = (currentAlignItemsValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignItemsValue + 1 : 0; -//// [[self node] setAlignItems:nextAlignItemsValue]; -// } -// -// [self updateInspectorWithLayoutable]; -//} -//- (void)setFlexBasisValue:(ASButtonNode *)sender -//{ -// [sender setSelected:!sender.isSelected]; // FIXME: fix ASControlNode documentation that this is automatic - unlike highlighted, it is up to the application to decide when a button should be selected or not. Selected is a more persistant thing and highlighted is for the moment, like as a user has a finger on it, -// FIXME: finish -//} -// -//- (void)setVizNodeInsets:(ASButtonNode *)sender -//{ -// BOOL newState = !sender.selected; -// -// if (newState == YES) { -// self.vizNodeInsetSize = 0; -// [self.delegate toggleVisualization:NO]; // FIXME -// [self.delegate toggleVisualization:YES]; // FIXME -// _vizNodeBordersBtn.selected = YES; -// -// } else { -// self.vizNodeInsetSize = 10; -// [self.delegate toggleVisualization:NO]; // FIXME -// [self.delegate toggleVisualization:YES]; // FIXME -// } -// -// sender.selected = newState; -//} -// -//- (void)setVizNodeBorders:(ASButtonNode *)sender -//{ -// BOOL newState = !sender.selected; -// -// [self.delegate toggleVisualization:newState]; // FIXME -// -// sender.selected = newState; -//} - -@end diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASChangeSetDataController.h b/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASChangeSetDataController.h deleted file mode 100644 index 49df1dc24d..0000000000 --- a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASChangeSetDataController.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// ASChangeSetDataController.h -// AsyncDisplayKit -// -// Created by Huy Nguyen on 19/10/15. -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#ifndef MINIMAL_ASDK - -#import - -/** - * @abstract Subclass of ASDataController that simulates ordering of operations in batch updates defined in UITableView and UICollectionView. - * - * @discussion The ordering is achieved by using _ASHierarchyChangeSet to enqueue and sort operations. - * More information about the ordering and the index paths used for operations can be found here: - * https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/TableView_iPhone/ManageInsertDeleteRow/ManageInsertDeleteRow.html#//apple_ref/doc/uid/TP40007451-CH10-SW17 - * - * @see ASDataController - * @see _ASHierarchyChangeSet - */ -@interface ASChangeSetDataController : ASDataController - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASChangeSetDataController.mm b/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASChangeSetDataController.mm deleted file mode 100644 index c41820dbb0..0000000000 --- a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASChangeSetDataController.mm +++ /dev/null @@ -1,212 +0,0 @@ -// -// ASChangeSetDataController.m -// AsyncDisplayKit -// -// Created by Huy Nguyen on 19/10/15. -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#ifndef MINIMAL_ASDK - -#import "ASChangeSetDataController.h" -#import "_ASHierarchyChangeSet.h" -#import "ASAssert.h" -#import "ASDataController+Subclasses.h" - -@implementation ASChangeSetDataController { - NSInteger _changeSetBatchUpdateCounter; - _ASHierarchyChangeSet *_changeSet; -} - -- (void)dealloc -{ - ASDisplayNodeCAssert(_changeSetBatchUpdateCounter == 0, @"ASChangeSetDataController deallocated in the middle of a batch update."); -} - -#pragma mark - Batching (External API) - -- (void)beginUpdates -{ - ASDisplayNodeAssertMainThread(); - if (_changeSetBatchUpdateCounter <= 0) { - _changeSetBatchUpdateCounter = 0; - _changeSet = [[_ASHierarchyChangeSet alloc] initWithOldData:[self itemCountsFromDataSource]]; - } - _changeSetBatchUpdateCounter++; -} - -- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion -{ - ASDisplayNodeAssertMainThread(); - _changeSetBatchUpdateCounter--; - - // Prevent calling endUpdatesAnimated:completion: in an unbalanced way - NSAssert(_changeSetBatchUpdateCounter >= 0, @"endUpdatesAnimated:completion: called without having a balanced beginUpdates call"); - - [_changeSet addCompletionHandler:completion]; - if (_changeSetBatchUpdateCounter == 0) { - void (^batchCompletion)(BOOL) = _changeSet.completionHandler; - - /** - * If the initial reloadData has not been called, just bail because we don't have - * our old data source counts. - * See ASUICollectionViewTests.testThatIssuingAnUpdateBeforeInitialReloadIsUnacceptable - * For the issue that UICollectionView has that we're choosing to workaround. - */ - if (!self.initialReloadDataHasBeenCalled) { - if (batchCompletion != nil) { - batchCompletion(YES); - } - _changeSet = nil; - return; - } - - [self invalidateDataSourceItemCounts]; - - // Attempt to mark the update completed. This is when update validation will occur inside the changeset. - // If an invalid update exception is thrown, we catch it and inject our "validationErrorSource" object, - // which is the table/collection node's data source, into the exception reason to help debugging. - @try { - [_changeSet markCompletedWithNewItemCounts:[self itemCountsFromDataSource]]; - } @catch (NSException *e) { - id responsibleDataSource = self.validationErrorSource; - if (e.name == ASCollectionInvalidUpdateException && responsibleDataSource != nil) { - [NSException raise:ASCollectionInvalidUpdateException format:@"%@: %@", [responsibleDataSource class], e.reason]; - } else { - @throw e; - } - } - - ASDataControllerLogEvent(self, @"triggeredUpdate: %@", _changeSet); - - [super beginUpdates]; - - for (_ASHierarchyItemChange *change in [_changeSet itemChangesOfType:_ASHierarchyChangeTypeDelete]) { - [super deleteRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions]; - } - - for (_ASHierarchySectionChange *change in [_changeSet sectionChangesOfType:_ASHierarchyChangeTypeDelete]) { - [super deleteSections:change.indexSet withAnimationOptions:change.animationOptions]; - } - - for (_ASHierarchySectionChange *change in [_changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) { - [super insertSections:change.indexSet withAnimationOptions:change.animationOptions]; - } - - for (_ASHierarchyItemChange *change in [_changeSet itemChangesOfType:_ASHierarchyChangeTypeInsert]) { - [super insertRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions]; - } - -#if ASEVENTLOG_ENABLE - NSString *changeSetDescription = ASObjectDescriptionMakeTiny(_changeSet); - batchCompletion = ^(BOOL finished) { - if (batchCompletion != nil) { - batchCompletion(finished); - } - ASDataControllerLogEvent(self, @"finishedUpdate: %@", changeSetDescription); - }; -#endif - - [super endUpdatesAnimated:animated completion:batchCompletion]; - - _changeSet = nil; - } -} - -- (BOOL)batchUpdating -{ - BOOL batchUpdating = (_changeSetBatchUpdateCounter != 0); - // _changeSet must be available during batch update - ASDisplayNodeAssertTrue(batchUpdating == (_changeSet != nil)); - return batchUpdating; -} - -- (void)waitUntilAllUpdatesAreCommitted -{ - ASDisplayNodeAssertMainThread(); - if (self.batchUpdating) { - // This assertion will be enabled soon. -// ASDisplayNodeFailAssert(@"Should not call %@ during batch update", NSStringFromSelector(_cmd)); - return; - } - - [super waitUntilAllUpdatesAreCommitted]; -} - -#pragma mark - Section Editing (External API) - -- (void)insertSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet insertSections:sections animationOptions:animationOptions]; - [self endUpdates]; -} - -- (void)deleteSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet deleteSections:sections animationOptions:animationOptions]; - [self endUpdates]; -} - -- (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet reloadSections:sections animationOptions:animationOptions]; - [self endUpdates]; -} - -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet deleteSections:[NSIndexSet indexSetWithIndex:section] animationOptions:animationOptions]; - [_changeSet insertSections:[NSIndexSet indexSetWithIndex:newSection] animationOptions:animationOptions]; - [self endUpdates]; -} - -#pragma mark - Row Editing (External API) - -- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet insertItems:indexPaths animationOptions:animationOptions]; - [self endUpdates]; -} - -- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet deleteItems:indexPaths animationOptions:animationOptions]; - [self endUpdates]; -} - -- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet reloadItems:indexPaths animationOptions:animationOptions]; - [self endUpdates]; -} - -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet deleteItems:@[indexPath] animationOptions:animationOptions]; - [_changeSet insertItems:@[newIndexPath] animationOptions:animationOptions]; - [self endUpdates]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASCollectionDataController.h b/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASCollectionDataController.h deleted file mode 100644 index adc1d8d957..0000000000 --- a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASCollectionDataController.h +++ /dev/null @@ -1,57 +0,0 @@ -// -// ASCollectionDataController.h -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#ifndef MINIMAL_ASDK - -#import - -#import -#import - -@class ASDisplayNode; -@class ASCollectionDataController; -@protocol ASSectionContext; - -NS_ASSUME_NONNULL_BEGIN - -@protocol ASCollectionDataControllerSource - -/** - The constrained size range for layout. - */ -- (ASSizeRange)dataController:(ASCollectionDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; - -- (NSArray *)supplementaryNodeKindsInDataController:(ASCollectionDataController *)dataController sections:(NSIndexSet *)sections; - -- (NSUInteger)dataController:(ASCollectionDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section; - -- (nullable id)dataController:(ASCollectionDataController *)dataController contextForSection:(NSInteger)section; - -@optional - -- (ASCellNode *)dataController:(ASCollectionDataController *)dataController supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; - -- (ASCellNodeBlock)dataController:(ASCollectionDataController *)dataController supplementaryNodeBlockOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; - -@end - -@interface ASCollectionDataController : ASDataController - -- (instancetype)initWithDataSource:(id)dataSource eventLog:(nullable ASEventLog *)eventLog NS_DESIGNATED_INITIALIZER; - -- (nullable ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; - -- (nullable id)contextForSection:(NSInteger)section; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASCollectionDataController.mm b/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASCollectionDataController.mm deleted file mode 100644 index 2dc4bb203c..0000000000 --- a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ /dev/null @@ -1,320 +0,0 @@ -// -// ASCollectionDataController.mm -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// -#ifndef MINIMAL_ASDK -#import - -#import -#import -#import -#import -#import -#import -#import -#import - -//#define LOG(...) NSLog(__VA_ARGS__) -#define LOG(...) - -@interface ASCollectionDataController () { - BOOL _dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath; - NSInteger _nextSectionID; - NSMutableArray *_sections; - NSArray *_pendingSections; - - /** - * supplementaryKinds can only be accessed on the main thread - * and so we set this in the -prepare stage, and then read it during the -will - * stage of each update operation. - */ - NSArray *_supplementaryKindsForPendingOperation; -} - -- (id)collectionDataSource; - -@end - -@implementation ASCollectionDataController { - NSMutableDictionary *> *_pendingNodeContexts; -} - -- (instancetype)initWithDataSource:(id)dataSource eventLog:(ASEventLog *)eventLog -{ - self = [super initWithDataSource:dataSource eventLog:eventLog]; - if (self != nil) { - _pendingNodeContexts = [NSMutableDictionary dictionary]; - _dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath = [dataSource respondsToSelector:@selector(dataController:supplementaryNodeBlockOfKind:atIndexPath:)]; - - ASDisplayNodeAssertTrue(_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath || [dataSource respondsToSelector:@selector(dataController:supplementaryNodeOfKind:atIndexPath:)]); - - _nextSectionID = 0; - _sections = [NSMutableArray array]; - } - return self; -} - -- (void)prepareForReloadDataWithSectionCount:(NSInteger)newSectionCount -{ - ASDisplayNodeAssertMainThread(); - NSIndexSet *sections = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, newSectionCount)]; - - [_sections removeAllObjects]; - [self _populatePendingSectionsFromDataSource:sections]; - - for (NSString *kind in [self supplementaryKindsInSections:sections]) { - LOG(@"Populating elements of kind: %@", kind); - NSMutableArray *contexts = [NSMutableArray array]; - [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableContexts:contexts]; - _pendingNodeContexts[kind] = contexts; - } -} - -- (void)willReloadDataWithSectionCount:(NSInteger)newSectionCount -{ - NSIndexSet *sectionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, newSectionCount)]; - - [self applyPendingSections:sectionIndexes]; - - // Assert that ASDataController has already deleted all the old sections for us. - ASDisplayNodeAssert([self editingNodesOfKind:ASDataControllerRowNodeKind].count == 0, @"Expected that all old sections were deleted before %@. Sections: %@", NSStringFromSelector(_cmd), [self editingNodesOfKind:ASDataControllerRowNodeKind]); - - [_pendingNodeContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray * _Nonnull contexts, __unused BOOL * _Nonnull stop) { - // Insert each section - NSMutableArray *sections = [NSMutableArray arrayWithCapacity:newSectionCount]; - for (int i = 0; i < newSectionCount; i++) { - [sections addObject:[NSMutableArray array]]; - } - [self insertSections:sections ofKind:kind atIndexSet:sectionIndexes completion:nil]; - - [self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; - }]; - }]; - [_pendingNodeContexts removeAllObjects]; -} - -- (void)prepareForInsertSections:(NSIndexSet *)sections -{ - ASDisplayNodeAssertMainThread(); - [self _populatePendingSectionsFromDataSource:sections]; - - for (NSString *kind in [self supplementaryKindsInSections:sections]) { - LOG(@"Populating elements of kind: %@, for sections: %@", kind, sections); - NSMutableArray *contexts = [NSMutableArray array]; - [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableContexts:contexts]; - _pendingNodeContexts[kind] = contexts; - } -} - -- (void)willInsertSections:(NSIndexSet *)sections -{ - [self applyPendingSections:sections]; - - [_pendingNodeContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray * _Nonnull contexts, BOOL * _Nonnull stop) { - NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count]; - for (NSUInteger i = 0; i < sections.count; i++) { - [sectionArray addObject:[NSMutableArray array]]; - } - - [self insertSections:sectionArray ofKind:kind atIndexSet:sections completion:nil]; - [self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; - }]; - }]; - [_pendingNodeContexts removeAllObjects]; -} - -- (void)willDeleteSections:(NSIndexSet *)sections -{ - [_sections removeObjectsAtIndexes:sections]; -} - -- (void)prepareForInsertRowsAtIndexPaths:(NSArray *)indexPaths -{ - ASDisplayNodeAssertMainThread(); - NSIndexSet *sections = [NSIndexSet as_sectionsFromIndexPaths:indexPaths]; - for (NSString *kind in [self supplementaryKindsInSections:sections]) { - LOG(@"Populating elements of kind: %@, for index paths: %@", kind, indexPaths); - NSMutableArray *contexts = [NSMutableArray array]; - [self _populateSupplementaryNodesOfKind:kind atIndexPaths:indexPaths mutableContexts:contexts]; - _pendingNodeContexts[kind] = contexts; - } -} - -- (void)willInsertRowsAtIndexPaths:(NSArray *)indexPaths -{ - [_pendingNodeContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray * _Nonnull contexts, BOOL * _Nonnull stop) { - [self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; - }]; - }]; - - [_pendingNodeContexts removeAllObjects]; -} - -- (void)prepareForDeleteRowsAtIndexPaths:(NSArray *)indexPaths -{ - ASDisplayNodeAssertMainThread(); - NSIndexSet *sections = [NSIndexSet as_sectionsFromIndexPaths:indexPaths]; - _supplementaryKindsForPendingOperation = [self supplementaryKindsInSections:sections]; - for (NSString *kind in _supplementaryKindsForPendingOperation) { - NSMutableArray *contexts = [NSMutableArray array]; - [self _populateSupplementaryNodesOfKind:kind atIndexPaths:indexPaths mutableContexts:contexts]; - _pendingNodeContexts[kind] = contexts; - } -} - -- (void)willDeleteRowsAtIndexPaths:(NSArray *)indexPaths -{ - for (NSString *kind in _supplementaryKindsForPendingOperation) { - NSArray *deletedIndexPaths = ASIndexPathsInMultidimensionalArrayIntersectingIndexPaths([self editingNodesOfKind:kind], indexPaths); - - [self deleteNodesOfKind:kind atIndexPaths:deletedIndexPaths completion:nil]; - - // If any of the contexts remain after the deletion, re-insert them, e.g. - // UICollectionElementKindSectionHeader remains even if item 0 is deleted. - NSMutableArray *reinsertedContexts = [NSMutableArray array]; - for (ASIndexedNodeContext *context in _pendingNodeContexts[kind]) { - if ([deletedIndexPaths containsObject:context.indexPath]) { - [reinsertedContexts addObject:context]; - } - } - - [self batchLayoutNodesFromContexts:reinsertedContexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; - }]; - } - [_pendingNodeContexts removeAllObjects]; - _supplementaryKindsForPendingOperation = nil; -} - -- (void)_populatePendingSectionsFromDataSource:(NSIndexSet *)sectionIndexes -{ - ASDisplayNodeAssertMainThread(); - - NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionIndexes.count]; - [sectionIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { - id context = [self.collectionDataSource dataController:self contextForSection:idx]; - [sections addObject:[[ASSection alloc] initWithSectionID:_nextSectionID context:context]]; - _nextSectionID++; - }]; - _pendingSections = sections; -} - -- (void)_populateSupplementaryNodesOfKind:(NSString *)kind withSections:(NSIndexSet *)sections mutableContexts:(NSMutableArray *)contexts -{ - __weak id environment = [self.environmentDelegate dataControllerEnvironment]; - - [sections enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { - for (NSUInteger sec = range.location; sec < NSMaxRange(range); sec++) { - NSUInteger itemCount = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:sec]; - for (NSUInteger i = 0; i < itemCount; i++) { - NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sec]; - [self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts environment:environment]; - } - } - }]; -} - -- (void)_populateSupplementaryNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths mutableContexts:(NSMutableArray *)contexts -{ - __weak id environment = [self.environmentDelegate dataControllerEnvironment]; - - NSMutableIndexSet *sections = [NSMutableIndexSet indexSet]; - for (NSIndexPath *indexPath in indexPaths) { - [sections addIndex:indexPath.section]; - } - - [sections enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { - for (NSUInteger sec = range.location; sec < NSMaxRange(range); sec++) { - NSUInteger itemCount = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:sec]; - for (NSUInteger i = 0; i < itemCount; i++) { - NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sec]; - [self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts environment:environment]; - } - } - }]; -} - -- (void)_populateSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath mutableContexts:(NSMutableArray *)contexts environment:(id)environment -{ - ASCellNodeBlock supplementaryCellBlock; - if (_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath) { - supplementaryCellBlock = [self.collectionDataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath]; - } else { - ASCellNode *supplementaryNode = [self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]; - supplementaryCellBlock = ^{ return supplementaryNode; }; - } - - ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; - ASIndexedNodeContext *context = [[ASIndexedNodeContext alloc] initWithNodeBlock:supplementaryCellBlock - indexPath:indexPath - supplementaryElementKind:kind - constrainedSize:constrainedSize - environment:environment]; - [contexts addObject:context]; -} - -#pragma mark - Sizing query - -- (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - if ([kind isEqualToString:ASDataControllerRowNodeKind]) { - return [super constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; - } else { - ASDisplayNodeAssertMainThread(); - return [self.collectionDataSource dataController:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:indexPath]; - } -} - -#pragma mark - External supplementary store and section context querying - -- (ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertMainThread(); - NSArray *nodesOfKind = [self completedNodesOfKind:kind]; - NSInteger section = indexPath.section; - if (section < nodesOfKind.count) { - NSArray *nodesOfKindInSection = nodesOfKind[section]; - NSInteger itemIndex = indexPath.item; - if (itemIndex < nodesOfKindInSection.count) { - return nodesOfKindInSection[itemIndex]; - } - } - return nil; -} - -- (id)contextForSection:(NSInteger)section -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssertTrue(section >= 0 && section < _sections.count); - return _sections[section].context; -} - -#pragma mark - Private Helpers - -- (NSArray *)supplementaryKindsInSections:(NSIndexSet *)sections -{ - return [self.collectionDataSource supplementaryNodeKindsInDataController:self sections:sections]; -} - -- (id)collectionDataSource -{ - return (id)self.dataSource; -} - -- (void)applyPendingSections:(NSIndexSet *)sectionIndexes -{ - [_sections insertObjects:_pendingSections atIndexes:sectionIndexes]; - _pendingSections = nil; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASDataController.mm b/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASDataController.mm deleted file mode 100644 index 047b59099a..0000000000 --- a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASDataController.mm +++ /dev/null @@ -1,1093 +0,0 @@ -// -// ASDataController.mm -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// -#ifndef MINIMAL_ASDK -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -//#define LOG(...) NSLog(__VA_ARGS__) -#define LOG(...) - -#define AS_MEASURE_AVOIDED_DATACONTROLLER_WORK 0 - -#define RETURN_IF_NO_DATASOURCE(val) if (_dataSource == nil) { return val; } -#define ASSERT_ON_EDITING_QUEUE ASDisplayNodeAssertNotNil(dispatch_get_specific(&kASDataControllerEditingQueueKey), @"%@ must be called on the editing transaction queue.", NSStringFromSelector(_cmd)) - -const static NSUInteger kASDataControllerSizingCountPerProcessor = 5; -const static char * kASDataControllerEditingQueueKey = "kASDataControllerEditingQueueKey"; -const static char * kASDataControllerEditingQueueContext = "kASDataControllerEditingQueueContext"; - -NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; -NSString * const ASCollectionInvalidUpdateException = @"ASCollectionInvalidUpdateException"; - -#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK -@interface ASDataController (AvoidedWorkMeasuring) -+ (void)_didLayoutNode; -+ (void)_expectToInsertNodes:(NSUInteger)count; -@end -#endif - -@interface ASDataController () { - NSMutableDictionary *_nodeContexts; // Main thread only. This is modified immediately during edits i.e. these are in the dataSource's index space. - NSMutableArray *_externalCompletedNodes; // Main thread only. External data access can immediately query this if available. - NSMutableDictionary *_completedNodes; // Main thread only. External data access can immediately query this if _externalCompletedNodes is unavailable. - NSMutableDictionary *_editingNodes; // Modified on _editingTransactionQueue only. Updates propagated to _completedNodes. - BOOL _itemCountsFromDataSourceAreValid; // Main thread only. - std::vector _itemCountsFromDataSource; // Main thread only. - - ASMainSerialQueue *_mainSerialQueue; - - dispatch_queue_t _editingTransactionQueue; // Serial background queue. Dispatches concurrent layout and manages _editingNodes. - dispatch_group_t _editingTransactionGroup; // Group of all edit transaction blocks. Useful for waiting. - - BOOL _initialReloadDataHasBeenCalled; - - BOOL _delegateDidInsertNodes; - BOOL _delegateDidDeleteNodes; - BOOL _delegateDidInsertSections; - BOOL _delegateDidDeleteSections; -} - -@end - -@implementation ASDataController - -#pragma mark - Lifecycle - -- (instancetype)initWithDataSource:(id)dataSource eventLog:(ASEventLog *)eventLog -{ - if (!(self = [super init])) { - return nil; - } - - _dataSource = dataSource; - -#if ASEVENTLOG_ENABLE - _eventLog = eventLog; -#endif - - _nodeContexts = [NSMutableDictionary dictionary]; - _completedNodes = [NSMutableDictionary dictionary]; - _editingNodes = [NSMutableDictionary dictionary]; - - _nodeContexts[ASDataControllerRowNodeKind] = [NSMutableArray array]; - _completedNodes[ASDataControllerRowNodeKind] = [NSMutableArray array]; - _editingNodes[ASDataControllerRowNodeKind] = [NSMutableArray array]; - - _mainSerialQueue = [[ASMainSerialQueue alloc] init]; - - const char *queueName = [[NSString stringWithFormat:@"org.AsyncDisplayKit.ASDataController.editingTransactionQueue:%p", self] cStringUsingEncoding:NSASCIIStringEncoding]; - _editingTransactionQueue = dispatch_queue_create(queueName, DISPATCH_QUEUE_SERIAL); - dispatch_queue_set_specific(_editingTransactionQueue, &kASDataControllerEditingQueueKey, &kASDataControllerEditingQueueContext, NULL); - _editingTransactionGroup = dispatch_group_create(); - - return self; -} - -- (instancetype)init -{ - ASDisplayNodeFailAssert(@"Failed to call designated initializer."); - id fakeDataSource = nil; - ASEventLog *eventLog = nil; - return [self initWithDataSource:fakeDataSource eventLog:eventLog]; -} - -- (void)setDelegate:(id)delegate -{ - if (_delegate == delegate) { - return; - } - - _delegate = delegate; - - // Interrogate our delegate to understand its capabilities, optimizing away expensive respondsToSelector: calls later. - _delegateDidInsertNodes = [_delegate respondsToSelector:@selector(dataController:didInsertNodes:atIndexPaths:withAnimationOptions:)]; - _delegateDidDeleteNodes = [_delegate respondsToSelector:@selector(dataController:didDeleteNodes:atIndexPaths:withAnimationOptions:)]; - _delegateDidInsertSections = [_delegate respondsToSelector:@selector(dataController:didInsertSections:atIndexSet:withAnimationOptions:)]; - _delegateDidDeleteSections = [_delegate respondsToSelector:@selector(dataController:didDeleteSectionsAtIndexSet:withAnimationOptions:)]; -} - -+ (NSUInteger)parallelProcessorCount -{ - static NSUInteger parallelProcessorCount; - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - parallelProcessorCount = [[NSProcessInfo processInfo] activeProcessorCount]; - }); - - return parallelProcessorCount; -} - -#pragma mark - Cell Layout - -- (void)batchLayoutNodesFromContexts:(NSArray *)contexts batchCompletion:(ASDataControllerCompletionBlock)batchCompletionHandler -{ - ASSERT_ON_EDITING_QUEUE; -#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK - [ASDataController _expectToInsertNodes:contexts.count]; -#endif - - if (contexts.count == 0) { - batchCompletionHandler(@[], @[]); - return; - } - - ASProfilingSignpostStart(2, _dataSource); - - NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor; - NSUInteger count = contexts.count; - - // Processing in batches - for (NSUInteger i = 0; i < count; i += blockSize) { - NSRange batchedRange = NSMakeRange(i, MIN(count - i, blockSize)); - NSArray *batchedContexts = [contexts subarrayWithRange:batchedRange]; - NSArray *nodes = [self _layoutNodesFromContexts:batchedContexts]; - NSArray *indexPaths = [ASIndexedNodeContext indexPathsFromContexts:batchedContexts]; - batchCompletionHandler(nodes, indexPaths); - } - - ASProfilingSignpostEnd(2, _dataSource); -} - -/** - * Measure and layout the given node with the constrained size range. - */ -- (void)_layoutNode:(ASCellNode *)node withConstrainedSize:(ASSizeRange)constrainedSize -{ - ASDisplayNodeAssert(ASSizeRangeHasSignificantArea(constrainedSize), @"Attempt to layout cell node with invalid size range %@", NSStringFromASSizeRange(constrainedSize)); - - CGRect frame = CGRectZero; - frame.size = [node layoutThatFits:constrainedSize].size; - node.frame = frame; -} - -/** - * Measures and defines the layout for each node in optimized batches on an editing queue, inserting the results into the backing store. - */ -- (void)_batchLayoutAndInsertNodesFromContexts:(NSArray *)contexts withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASSERT_ON_EDITING_QUEUE; - - [self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { - // Insert finished nodes into data storage - [self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; -} - -- (NSArray *)_layoutNodesFromContexts:(NSArray *)contexts -{ - ASSERT_ON_EDITING_QUEUE; - - NSUInteger nodeCount = contexts.count; - if (!nodeCount || _dataSource == nil) { - return nil; - } - - __strong ASCellNode **allocatedNodeBuffer = (__strong ASCellNode **)calloc(nodeCount, sizeof(ASCellNode *)); - - dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - ASDispatchApply(nodeCount, queue, 0, ^(size_t i) { - RETURN_IF_NO_DATASOURCE(); - - // Allocate the node. - ASIndexedNodeContext *context = contexts[i]; - ASCellNode *node = context.node; - if (node == nil) { - ASDisplayNodeAssertNotNil(node, @"Node block created nil node; %@, %@", self, self.dataSource); - node = [[ASCellNode alloc] init]; // Fallback to avoid crash for production apps. - } - - // Layout the node if the size range is valid. - ASSizeRange sizeRange = context.constrainedSize; - if (ASSizeRangeHasSignificantArea(sizeRange)) { - [self _layoutNode:node withConstrainedSize:sizeRange]; - } - -#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK - [ASDataController _didLayoutNode]; -#endif - allocatedNodeBuffer[i] = node; - }); - - BOOL canceled = _dataSource == nil; - - // Create nodes array - NSArray *nodes = canceled ? nil : [NSArray arrayWithObjects:allocatedNodeBuffer count:nodeCount]; - - // Nil out buffer indexes to allow arc to free the stored cells. - for (int i = 0; i < nodeCount; i++) { - allocatedNodeBuffer[i] = nil; - } - free(allocatedNodeBuffer); - - return nodes; -} - -- (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - return [_dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPath]; -} - -#pragma mark - External Data Querying + Editing - -- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(ASDataControllerCompletionBlock)completionBlock -{ - ASSERT_ON_EDITING_QUEUE; - if (!indexPaths.count || _dataSource == nil) { - return; - } - - NSMutableArray *editingNodes = _editingNodes[kind]; - ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths, nodes); - - // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. - NSMutableArray *completedNodes = ASTwoDimensionalArrayDeepMutableCopy(editingNodes); - - [_mainSerialQueue performBlockOnMainThread:^{ - _completedNodes[kind] = completedNodes; - if (completionBlock) { - completionBlock(nodes, indexPaths); - } - }]; -} - -- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(ASDataControllerCompletionBlock)completionBlock -{ - ASSERT_ON_EDITING_QUEUE; - if (!indexPaths.count || _dataSource == nil) { - return; - } - - LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@, full index paths in _editingNodes = %@", indexPaths, kind, ASIndexPathsForTwoDimensionalArray(_editingNodes[kind])); - ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[kind], indexPaths); - - [_mainSerialQueue performBlockOnMainThread:^{ - NSMutableArray *allNodes = _completedNodes[kind]; - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(allNodes, indexPaths); - ASDeleteElementsInMultidimensionalArrayAtIndexPaths(allNodes, indexPaths); - if (completionBlock) { - completionBlock(nodes, indexPaths); - } - }]; -} - -- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock -{ - ASSERT_ON_EDITING_QUEUE; - if (!indexSet.count|| _dataSource == nil) { - return; - } - - if (_editingNodes[kind] == nil) { - _editingNodes[kind] = [NSMutableArray array]; - } - - [_editingNodes[kind] insertObjects:sections atIndexes:indexSet]; - - // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. - NSArray *sectionsForCompleted = ASTwoDimensionalArrayDeepMutableCopy(sections); - - [_mainSerialQueue performBlockOnMainThread:^{ - [_completedNodes[kind] insertObjects:sectionsForCompleted atIndexes:indexSet]; - if (completionBlock) { - completionBlock(sections, indexSet); - } - }]; -} - -- (void)deleteSections:(NSIndexSet *)indexSet completion:(void (^)())completionBlock -{ - ASSERT_ON_EDITING_QUEUE; - if (!indexSet.count || _dataSource == nil) { - return; - } - - [_editingNodes enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray *sections, BOOL * _Nonnull stop) { - [sections removeObjectsAtIndexes:indexSet]; - }]; - [_mainSerialQueue performBlockOnMainThread:^{ - [_completedNodes enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray *sections, BOOL * _Nonnull stop) { - [sections removeObjectsAtIndexes:indexSet]; - }]; - if (completionBlock) { - completionBlock(); - } - }]; -} - -#pragma mark - Internal Data Querying + Editing - -/** - * Inserts the specified nodes into the given index paths and notifies the delegate of newly inserted nodes. - * - * @discussion Nodes are first inserted into the editing store, then the completed store is replaced by a deep copy - * of the editing nodes. The delegate is invoked on the main thread. - */ -- (void)_insertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASSERT_ON_EDITING_QUEUE; - - [self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - ASDisplayNodeAssertMainThread(); - - if (_delegateDidInsertNodes) - [_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; -} - -/** - * Removes the specified nodes at the given index paths and notifies the delegate of the nodes removed. - * - * @discussion Nodes are first removed from the editing store then removed from the completed store on the main thread. - * Once the backing stores are consistent, the delegate is invoked on the main thread. - */ -- (void)_deleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASSERT_ON_EDITING_QUEUE; - - [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - ASDisplayNodeAssertMainThread(); - - if (_delegateDidDeleteNodes) - [_delegate dataController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; -} - -/** - * Inserts sections, represented as arrays, into the backing store at the given indices and notifies the delegate. - * - * @discussion The section arrays are inserted into the editing store, then a deep copy of the sections are inserted - * in the completed store on the main thread. The delegate is invoked on the main thread. - */ -- (void)_insertSections:(NSMutableArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASSERT_ON_EDITING_QUEUE; - - [self insertSections:sections ofKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSArray *sections, NSIndexSet *indexSet) { - ASDisplayNodeAssertMainThread(); - - if (_delegateDidInsertSections) - [_delegate dataController:self didInsertSections:sections atIndexSet:indexSet withAnimationOptions:animationOptions]; - }]; -} - -/** - * Removes sections at the given indices from the backing store and notifies the delegate. - * - * @discussion Section array are first removed from the editing store, then the associated section in the completed - * store is removed on the main thread. The delegate is invoked on the main thread. - */ -- (void)_deleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASSERT_ON_EDITING_QUEUE; - - [self deleteSections:indexSet completion:^() { - ASDisplayNodeAssertMainThread(); - - if (_delegateDidDeleteSections) - [_delegate dataController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; - }]; -} - -#pragma mark - Initial Load & Full Reload (External API) - -- (void)reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions completion:(void (^)())completion -{ - [self _reloadDataWithAnimationOptions:animationOptions synchronously:NO completion:completion]; -} - -- (void)reloadDataImmediatelyWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - [self _reloadDataWithAnimationOptions:animationOptions synchronously:YES completion:nil]; -} - -- (void)_reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions synchronously:(BOOL)synchronously completion:(void (^)())completion -{ - ASDisplayNodeAssertMainThread(); - - _initialReloadDataHasBeenCalled = YES; - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - - [self invalidateDataSourceItemCounts]; - NSUInteger sectionCount = [self itemCountsFromDataSource].size(); - NSIndexSet *sectionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)]; - NSArray *newContexts = [self _populateNodeContextsFromDataSourceForSections:sectionIndexes]; - - // Update _nodeContexts - NSMutableArray *allContexts = _nodeContexts[ASDataControllerRowNodeKind]; - [allContexts removeAllObjects]; - NSArray *nodeIndexPaths = [ASIndexedNodeContext indexPathsFromContexts:newContexts]; - for (int i = 0; i < sectionCount; i++) { - [allContexts addObject:[[NSMutableArray alloc] init]]; - } - ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(allContexts, nodeIndexPaths, newContexts); - - // Allow subclasses to perform setup before going into the edit transaction - [self prepareForReloadDataWithSectionCount:sectionCount]; - - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - LOG(@"Edit Transaction - reloadData"); - - /** - * Leave the current data in the collection view until the first batch of nodes are laid out. - * Once the first batch is laid out, in one operation, replace all the sections and insert - * the first batch of items. - * - * We previously would replace all the sections immediately, and then start adding items as they - * were laid out. This resulted in more traffic to the UICollectionView and it also caused all the - * section headers to bunch up until the items come and fill out the sections. - */ - __block BOOL isFirstBatch = YES; - [self batchLayoutNodesFromContexts:newContexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { - if (isFirstBatch) { - // -beginUpdates - [_mainSerialQueue performBlockOnMainThread:^{ - [_delegate dataControllerBeginUpdates:self]; - [_delegate dataControllerWillDeleteAllData:self]; - }]; - - // deleteSections: - // Remove everything that existed before the reload, now that we're ready to insert replacements - NSUInteger oldSectionCount = [_editingNodes[ASDataControllerRowNodeKind] count]; - if (oldSectionCount) { - NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, oldSectionCount)]; - [self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; - } - - [self willReloadDataWithSectionCount:sectionCount]; - - // insertSections: - NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; - for (int i = 0; i < sectionCount; i++) { - [sections addObject:[[NSMutableArray alloc] init]]; - } - [self _insertSections:sections atIndexSet:sectionIndexes withAnimationOptions:animationOptions]; - } - - // insertItemsAtIndexPaths: - [self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - - if (isFirstBatch) { - // -endUpdates - [_mainSerialQueue performBlockOnMainThread:^{ - [_delegate dataController:self endUpdatesAnimated:NO completion:nil]; - }]; - isFirstBatch = NO; - } - }]; - - if (completion) { - [_mainSerialQueue performBlockOnMainThread:completion]; - } - }); - if (synchronously) { - [self waitUntilAllUpdatesAreCommitted]; - } -} - -- (void)waitUntilAllUpdatesAreCommitted -{ - ASDisplayNodeAssertMainThread(); - - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - - // Schedule block in main serial queue to wait until all operations are finished that are - // where scheduled while waiting for the _editingTransactionQueue to finish - [_mainSerialQueue performBlockOnMainThread:^{ }]; -} - -#pragma mark - Data Source Access (Calling _dataSource) - -/** - * Fetches row contexts for the provided sections from the data source. - */ -- (NSArray *)_populateNodeContextsFromDataSourceForSections:(NSIndexSet *)sections -{ - ASDisplayNodeAssertMainThread(); - - __weak id environment = [self.environmentDelegate dataControllerEnvironment]; - - std::vector counts = [self itemCountsFromDataSource]; - NSMutableArray *contexts = [NSMutableArray array]; - [sections enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { - for (NSUInteger sectionIndex = range.location; sectionIndex < NSMaxRange(range); sectionIndex++) { - NSUInteger itemCount = counts[sectionIndex]; - for (NSUInteger i = 0; i < itemCount; i++) { - NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sectionIndex]; - ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath]; - - ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath]; - [contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock - indexPath:indexPath - supplementaryElementKind:nil - constrainedSize:constrainedSize - environment:environment]]; - } - } - }]; - return contexts; -} - -- (void)invalidateDataSourceItemCounts -{ - ASDisplayNodeAssertMainThread(); - _itemCountsFromDataSourceAreValid = NO; -} - -- (std::vector)itemCountsFromDataSource -{ - ASDisplayNodeAssertMainThread(); - if (NO == _itemCountsFromDataSourceAreValid) { - id source = self.dataSource; - NSInteger sectionCount = [source numberOfSectionsInDataController:self]; - std::vector newCounts; - newCounts.reserve(sectionCount); - for (NSInteger i = 0; i < sectionCount; i++) { - newCounts.push_back([source dataController:self rowsInSection:i]); - } - _itemCountsFromDataSource = newCounts; - _itemCountsFromDataSourceAreValid = YES; - } - return _itemCountsFromDataSource; -} - -#pragma mark - Batching (External API) - -- (void)beginUpdates -{ - ASDisplayNodeAssertMainThread(); - // TODO: make this -waitUntilAllUpdatesAreCommitted? - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [_mainSerialQueue performBlockOnMainThread:^{ - // Deep copy _completedNodes to _externalCompletedNodes. - // Any external queries from now on will be done on _externalCompletedNodes, to guarantee data consistency with the delegate. - _externalCompletedNodes = ASTwoDimensionalArrayDeepMutableCopy(_completedNodes[ASDataControllerRowNodeKind]); - - LOG(@"beginUpdates - begin updates call to delegate"); - [_delegate dataControllerBeginUpdates:self]; - }]; - }); -} - -- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion -{ - LOG(@"endUpdatesWithCompletion - beginning"); - ASDisplayNodeAssertMainThread(); - - // Running these commands may result in blocking on an _editingTransactionQueue operation that started even before -beginUpdates. - // Each subsequent command in the queue will also wait on the full asynchronous completion of the prior command's edit transaction. - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [_mainSerialQueue performBlockOnMainThread:^{ - // Now that the transaction is done, _completedNodes can be accessed externally again. - _externalCompletedNodes = nil; - - LOG(@"endUpdatesWithCompletion - calling delegate end"); - [_delegate dataController:self endUpdatesAnimated:animated completion:completion]; - }]; - }); -} - -- (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet animated:(BOOL)animated -{ - ASDisplayNodeAssertMainThread(); - - void (^batchCompletion)(BOOL) = changeSet.completionHandler; - - /** - * If the initial reloadData has not been called, just bail because we don't have - * our old data source counts. - * See ASUICollectionViewTests.testThatIssuingAnUpdateBeforeInitialReloadIsUnacceptable - * For the issue that UICollectionView has that we're choosing to workaround. - */ - if (!self.initialReloadDataHasBeenCalled) { - if (batchCompletion != nil) { - batchCompletion(YES); - } - return; - } - - [self invalidateDataSourceItemCounts]; - - // Attempt to mark the update completed. This is when update validation will occur inside the changeset. - // If an invalid update exception is thrown, we catch it and inject our "validationErrorSource" object, - // which is the table/collection node's data source, into the exception reason to help debugging. - @try { - [changeSet markCompletedWithNewItemCounts:[self itemCountsFromDataSource]]; - } @catch (NSException *e) { - id responsibleDataSource = self.validationErrorSource; - if (e.name == ASCollectionInvalidUpdateException && responsibleDataSource != nil) { - [NSException raise:ASCollectionInvalidUpdateException format:@"%@: %@", [responsibleDataSource class], e.reason]; - } else { - @throw e; - } - } - - ASDataControllerLogEvent(self, @"triggeredUpdate: %@", changeSet); - - [self beginUpdates]; - - for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeDelete]) { - [self deleteRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions]; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeDelete]) { - [self deleteSections:change.indexSet withAnimationOptions:change.animationOptions]; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) { - [self insertSections:change.indexSet withAnimationOptions:change.animationOptions]; - } - - for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeInsert]) { - [self insertRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions]; - } - -#if ASEVENTLOG_ENABLE - NSString *changeSetDescription = ASObjectDescriptionMakeTiny(changeSet); - batchCompletion = ^(BOOL finished) { - if (batchCompletion != nil) { - batchCompletion(finished); - } - ASDataControllerLogEvent(self, @"finishedUpdate: %@", changeSetDescription); - }; -#endif - - [self endUpdatesAnimated:animated completion:batchCompletion]; -} - -#pragma mark - Section Editing (External API) - -- (void)insertSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - LOG(@"Edit Command - insertSections: %@", sections); - if (!_initialReloadDataHasBeenCalled) { - return; - } - - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - - NSArray *contexts = [self _populateNodeContextsFromDataSourceForSections:sections]; - - // Update _nodeContexts - { - NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count]; - for (NSUInteger i = 0; i < sections.count; i++) { - [sectionArray addObject:[NSMutableArray array]]; - } - NSMutableArray *allRowContexts = _nodeContexts[ASDataControllerRowNodeKind]; - [allRowContexts insertObjects:sectionArray atIndexes:sections]; - ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(allRowContexts, [ASIndexedNodeContext indexPathsFromContexts:contexts], contexts); - } - - [self prepareForInsertSections:sections]; - - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [self willInsertSections:sections]; - - LOG(@"Edit Transaction - insertSections: %@", sections); - NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count]; - for (NSUInteger i = 0; i < sections.count; i++) { - [sectionArray addObject:[NSMutableArray array]]; - } - - [self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions]; - - [self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions]; - }); -} - -- (void)deleteSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - LOG(@"Edit Command - deleteSections: %@", sections); - if (!_initialReloadDataHasBeenCalled) { - return; - } - - [_nodeContexts[ASDataControllerRowNodeKind] removeObjectsAtIndexes:sections]; - - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - - [self prepareForDeleteSections:sections]; - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [self willDeleteSections:sections]; - - // remove elements - LOG(@"Edit Transaction - deleteSections: %@", sections); - [self _deleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; - }); -} - -#pragma mark - Backing store manipulation optional hooks (Subclass API) - -- (void)prepareForReloadDataWithSectionCount:(NSInteger)newSectionCount -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)willReloadDataWithSectionCount:(NSInteger)newSectionCount -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)prepareForInsertSections:(NSIndexSet *)sections -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)prepareForDeleteSections:(NSIndexSet *)sections -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)willInsertSections:(NSIndexSet *)sections -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)willDeleteSections:(NSIndexSet *)sections -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)prepareForInsertRowsAtIndexPaths:(NSArray *)indexPaths -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)willInsertRowsAtIndexPaths:(NSArray *)indexPaths -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)prepareForDeleteRowsAtIndexPaths:(NSArray *)indexPaths -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)willDeleteRowsAtIndexPaths:(NSArray *)indexPaths -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -#pragma mark - Row Editing (External API) - -- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - if (!_initialReloadDataHasBeenCalled) { - return; - } - - LOG(@"Edit Command - insertRows: %@", indexPaths); - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - - // Sort indexPath to avoid messing up the index when inserting in several batches - NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; - NSMutableArray *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; - - __weak id environment = [self.environmentDelegate dataControllerEnvironment]; - - for (NSIndexPath *indexPath in sortedIndexPaths) { - ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath]; - ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath]; - [contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock - indexPath:indexPath - supplementaryElementKind:nil - constrainedSize:constrainedSize - environment:environment]]; - } - - ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(_nodeContexts[ASDataControllerRowNodeKind], sortedIndexPaths, contexts); - [self prepareForInsertRowsAtIndexPaths:indexPaths]; - - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [self willInsertRowsAtIndexPaths:indexPaths]; - - LOG(@"Edit Transaction - insertRows: %@", indexPaths); - [self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions]; - }); -} - -- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - - if (!_initialReloadDataHasBeenCalled) { - return; - } - - LOG(@"Edit Command - deleteRows: %@", indexPaths); - - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - - // Sort indexPath in order to avoid messing up the index when deleting in several batches. - // FIXME: Shouldn't deletes be sorted in descending order? - NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; - - ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_nodeContexts[ASDataControllerRowNodeKind], sortedIndexPaths); - [self prepareForDeleteRowsAtIndexPaths:sortedIndexPaths]; - - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [self willDeleteRowsAtIndexPaths:sortedIndexPaths]; - - LOG(@"Edit Transaction - deleteRows: %@", indexPaths); - [self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions]; - }); -} - -- (void)relayoutAllNodes -{ - ASDisplayNodeAssertMainThread(); - if (!_initialReloadDataHasBeenCalled) { - return; - } - - LOG(@"Edit Command - relayoutRows"); - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - - // Can't relayout right away because _completedNodes may not be up-to-date, - // i.e there might be some nodes that were measured using the old constrained size but haven't been added to _completedNodes - // (see _layoutNodes:atIndexPaths:withAnimationOptions:). - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [_mainSerialQueue performBlockOnMainThread:^{ - for (NSString *kind in _completedNodes) { - [self _relayoutNodesOfKind:kind]; - } - }]; - }); -} - -- (void)_relayoutNodesOfKind:(NSString *)kind -{ - ASDisplayNodeAssertMainThread(); - NSArray *nodes = [self completedNodesOfKind:kind]; - if (!nodes.count) { - return; - } - - NSUInteger sectionIndex = 0; - for (NSMutableArray *section in nodes) { - NSUInteger rowIndex = 0; - for (ASCellNode *node in section) { - RETURN_IF_NO_DATASOURCE(); - NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex]; - ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; - if (ASSizeRangeHasSignificantArea(constrainedSize)) { - [self _layoutNode:node withConstrainedSize:constrainedSize]; - } - rowIndex += 1; - } - sectionIndex += 1; - } -} - -#pragma mark - Data Querying (Subclass API) - -- (NSMutableArray *)editingNodesOfKind:(NSString *)kind -{ - return _editingNodes[kind] ? : [NSMutableArray array]; -} - -- (NSMutableArray *)completedNodesOfKind:(NSString *)kind -{ - return _completedNodes[kind]; -} - -#pragma mark - Data Querying (External API) - -- (NSUInteger)numberOfSections -{ - ASDisplayNodeAssertMainThread(); - return [_nodeContexts[ASDataControllerRowNodeKind] count]; -} - -- (NSUInteger)numberOfRowsInSection:(NSUInteger)section -{ - ASDisplayNodeAssertMainThread(); - NSArray *contextSections = _nodeContexts[ASDataControllerRowNodeKind]; - return (section < contextSections.count) ? [contextSections[section] count] : 0; -} - -- (NSUInteger)completedNumberOfSections -{ - ASDisplayNodeAssertMainThread(); - return [[self completedNodes] count]; -} - -- (NSUInteger)completedNumberOfRowsInSection:(NSUInteger)section -{ - ASDisplayNodeAssertMainThread(); - NSArray *completedNodes = [self completedNodes]; - return (section < completedNodes.count) ? [completedNodes[section] count] : 0; -} - -- (ASCellNode *)nodeAtIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertMainThread(); - if (indexPath == nil) { - return nil; - } - - NSArray *contexts = _nodeContexts[ASDataControllerRowNodeKind]; - NSInteger section = indexPath.section; - NSInteger row = indexPath.row; - ASIndexedNodeContext *context = nil; - - if (section >= 0 && row >= 0 && section < contexts.count) { - NSArray *completedNodesSection = contexts[section]; - if (row < completedNodesSection.count) { - context = completedNodesSection[row]; - } - } - return context.node; -} - -- (ASCellNode *)nodeAtCompletedIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertMainThread(); - if (indexPath == nil) { - return nil; - } - - NSArray *completedNodes = [self completedNodes]; - NSInteger section = indexPath.section; - NSInteger row = indexPath.row; - ASCellNode *node = nil; - - if (section >= 0 && row >= 0 && section < completedNodes.count) { - NSArray *completedNodesSection = completedNodes[section]; - if (row < completedNodesSection.count) { - node = completedNodesSection[row]; - } - } - - return node; -} - -- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode; -{ - ASDisplayNodeAssertMainThread(); - if (cellNode == nil) { - return nil; - } - - NSString *kind = cellNode.supplementaryElementKind ?: ASDataControllerRowNodeKind; - NSArray *contexts = _nodeContexts[kind]; - - // Check if the cached index path is still correct. - NSIndexPath *indexPath = cellNode.cachedIndexPath; - if (indexPath != nil) { - ASIndexedNodeContext *context = ASGetElementInTwoDimensionalArray(contexts, indexPath); - if (context.nodeIfAllocated == cellNode) { - return indexPath; - } else { - indexPath = nil; - } - } - - // Loop through each section to look for the node context - NSInteger section = 0; - for (NSArray *nodeContexts in contexts) { - NSUInteger item = [nodeContexts indexOfObjectPassingTest:^BOOL(ASIndexedNodeContext * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - return obj.nodeIfAllocated == cellNode; - }]; - if (item != NSNotFound) { - indexPath = [NSIndexPath indexPathForItem:item inSection:section]; - break; - } - section += 1; - } - cellNode.cachedIndexPath = indexPath; - return indexPath; -} - -- (NSIndexPath *)completedIndexPathForNode:(ASCellNode *)cellNode -{ - ASDisplayNodeAssertMainThread(); - if (cellNode == nil) { - return nil; - } - - NSInteger section = 0; - // Loop through each section to look for the cellNode - NSString *kind = cellNode.supplementaryElementKind ?: ASDataControllerRowNodeKind; - for (NSArray *sectionNodes in [self completedNodesOfKind:kind]) { - NSUInteger item = [sectionNodes indexOfObjectIdenticalTo:cellNode]; - if (item != NSNotFound) { - return [NSIndexPath indexPathForItem:item inSection:section]; - } - section += 1; - } - - return nil; -} - -/// Returns nodes that can be queried externally. _externalCompletedNodes is used if available, _completedNodes otherwise. -- (NSArray *)completedNodes -{ - ASDisplayNodeAssertMainThread(); - return _externalCompletedNodes ? : _completedNodes[ASDataControllerRowNodeKind]; -} - -- (void)moveCompletedNodeAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath -{ - ASDisplayNodeAssertMainThread(); - ASMoveElementInTwoDimensionalArray(_externalCompletedNodes, indexPath, newIndexPath); - ASMoveElementInTwoDimensionalArray(_completedNodes[ASDataControllerRowNodeKind], indexPath, newIndexPath); -} - -@end - -#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK - -static volatile int64_t _totalExpectedItems = 0; -static volatile int64_t _totalMeasuredNodes = 0; - -@implementation ASDataController (WorkMeasuring) - -+ (void)_didLayoutNode -{ - int64_t measured = OSAtomicIncrement64(&_totalMeasuredNodes); - int64_t expected = _totalExpectedItems; - if (measured % 20 == 0 || measured == expected) { - NSLog(@"Data controller avoided work (underestimated): %lld / %lld", measured, expected); - } -} - -+ (void)_expectToInsertNodes:(NSUInteger)count -{ - OSAtomicAdd64((int64_t)count, &_totalExpectedItems); -} - -@end -#endif - -#endif diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASFlowLayoutController.h b/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASFlowLayoutController.h deleted file mode 100644 index 7eeb4d3533..0000000000 --- a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASFlowLayoutController.h +++ /dev/null @@ -1,44 +0,0 @@ -// -// ASFlowLayoutController.h -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// -#ifndef MINIMAL_ASDK -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@class ASCellNode; - -typedef NS_ENUM(NSUInteger, ASFlowLayoutDirection) { - ASFlowLayoutDirectionVertical, - ASFlowLayoutDirectionHorizontal, -}; - -@protocol ASFlowLayoutControllerDataSource - -- (NSArray *> *)completedNodes; // This provides access to ASDataController's _completedNodes multidimensional array. - -@end - -/** - * An optimized flow layout controller that supports only vertical or horizontal scrolling, not simultaneously two-dimensional scrolling. - * It is used for all ASTableViews, and may be used with ASCollectionView. - */ -@interface ASFlowLayoutController : ASAbstractLayoutController - -@property (nonatomic, readonly, assign) ASFlowLayoutDirection layoutDirection; -@property (nonatomic, readwrite, weak) id dataSource; - -- (instancetype)initWithScrollOption:(ASFlowLayoutDirection)layoutDirection; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASFlowLayoutController.mm b/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASFlowLayoutController.mm deleted file mode 100644 index 4a23ec25a6..0000000000 --- a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASFlowLayoutController.mm +++ /dev/null @@ -1,203 +0,0 @@ -// -// ASFlowLayoutController.mm -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// -#ifndef MINIMAL_ASDK -#import -#import -#import -#import -#import - -#include -#include - -@interface ASFlowLayoutController() -{ - ASIndexPathRange _visibleRange; - std::vector _rangesByType; // All ASLayoutRangeTypes besides visible. -} - -@end - -@implementation ASFlowLayoutController - -- (instancetype)initWithScrollOption:(ASFlowLayoutDirection)layoutDirection -{ - if (!(self = [super init])) { - return nil; - } - _layoutDirection = layoutDirection; - _rangesByType = std::vector(ASLayoutRangeTypeCount); - return self; -} - -#pragma mark - Visible Indices - -- (void)setVisibleNodeIndexPaths:(NSArray *)indexPaths -{ - _visibleRange = [self indexPathRangeForIndexPaths:indexPaths]; -} - -/** - * IndexPath array for the element in the working range. - */ - -- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - CGSize viewportSize = [self viewportSize]; - - CGFloat viewportDirectionalSize = 0.0; - ASDirectionalScreenfulBuffer directionalBuffer = { 0, 0 }; - ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeMode:rangeMode rangeType:rangeType]; - - if (_layoutDirection == ASFlowLayoutDirectionHorizontal) { - viewportDirectionalSize = viewportSize.width; - directionalBuffer = ASDirectionalScreenfulBufferHorizontal(scrollDirection, tuningParameters); - } else { - viewportDirectionalSize = viewportSize.height; - directionalBuffer = ASDirectionalScreenfulBufferVertical(scrollDirection, tuningParameters); - } - - ASIndexPath startPath = [self findIndexPathAtDistance:(-directionalBuffer.negativeDirection * viewportDirectionalSize) - fromIndexPath:_visibleRange.start]; - - ASIndexPath endPath = [self findIndexPathAtDistance:(directionalBuffer.positiveDirection * viewportDirectionalSize) - fromIndexPath:_visibleRange.end]; - - ASDisplayNodeAssert(startPath.section <= endPath.section, @"startPath should never begin at a further position than endPath"); - - NSMutableSet *indexPathSet = [[NSMutableSet alloc] init]; - - NSArray *completedNodes = [_dataSource completedNodes]; - - ASIndexPath currPath = startPath; - - while (!ASIndexPathEqualToIndexPath(currPath, endPath)) { - [indexPathSet addObject:[NSIndexPath indexPathWithASIndexPath:currPath]]; - currPath.row++; - - // Once we reach the end of the section, advance to the next one. Keep advancing if the next section is zero-sized. - while (currPath.row >= [(NSArray *)completedNodes[currPath.section] count] && currPath.section < endPath.section) { - currPath.row = 0; - currPath.section++; - } - } - ASDisplayNodeAssert(currPath.section <= endPath.section, @"currPath should never reach a further section than endPath"); - - [indexPathSet addObject:[NSIndexPath indexPathWithASIndexPath:endPath]]; - - return indexPathSet; -} - -#pragma mark - Utility - -- (ASIndexPathRange)indexPathRangeForIndexPaths:(NSArray *)indexPaths -{ - // Set up an initial value so the MIN and MAX can work in the enumeration. - __block ASIndexPath currentIndexPath = [[indexPaths firstObject] ASIndexPathValue]; - __block ASIndexPathRange range; - range.start = currentIndexPath; - range.end = currentIndexPath; - - for (NSIndexPath *indexPath in indexPaths) { - currentIndexPath = [indexPath ASIndexPathValue]; - range.start = ASIndexPathMinimum(range.start, currentIndexPath); - range.end = ASIndexPathMaximum(range.end, currentIndexPath); - } - return range; -} - -- (ASIndexPath)findIndexPathAtDistance:(CGFloat)distance fromIndexPath:(ASIndexPath)start -{ - // "end" is the index path we'll advance until we have gone far enough from "start" to reach "distance" - ASIndexPath end = start; - // "previous" will store one iteration before "end", in case we go too far and need to reset "end" to be "previous" - ASIndexPath previous = start; - - NSArray *completedNodes = [_dataSource completedNodes]; - NSUInteger numberOfSections = [completedNodes count]; - NSUInteger numberOfRowsInSection = [(NSArray *)completedNodes[end.section] count]; - - // If "distance" is negative, advance "end" backwards across rows and sections. - // Otherwise, advance forward. In either case, bring "distance" closer to zero by the dimension of each row passed. - if (distance < 0.0 && end.section >= 0 && end.section < numberOfSections && end.row >= 0 && end.row < numberOfRowsInSection) { - while (distance < 0.0 && end.section >= 0 && end.row >= 0) { - previous = end; - ASDisplayNode *node = completedNodes[end.section][end.row]; - CGSize size = node.calculatedSize; - distance += (_layoutDirection == ASFlowLayoutDirectionHorizontal ? size.width : size.height); - end.row--; - // If we've gone to a negative row, set to the last row of the previous section. While loop is required to handle empty sections. - while (end.row < 0 && end.section > 0) { - end.section--; - numberOfRowsInSection = [(NSArray *)completedNodes[end.section] count]; - end.row = numberOfRowsInSection - 1; - } - } - - if (end.row < 0) { - end = previous; - } - } else { - while (distance > 0.0 && end.section >= 0 && end.section < numberOfSections && end.row >= 0 && end.row < numberOfRowsInSection) { - previous = end; - ASDisplayNode *node = completedNodes[end.section][end.row]; - CGSize size = node.calculatedSize; - distance -= _layoutDirection == ASFlowLayoutDirectionHorizontal ? size.width : size.height; - - end.row++; - // If we've gone beyond the section, reset to the beginning of the next section. While loop is required to handle empty sections. - while (end.row >= numberOfRowsInSection && end.section < numberOfSections - 1) { - end.row = 0; - end.section++; - numberOfRowsInSection = [(NSArray *)completedNodes[end.section] count]; - } - } - - if (end.row >= numberOfRowsInSection) { - end = previous; - } - } - - return end; -} - -- (NSInteger)flowLayoutDistanceForRange:(ASIndexPathRange)range -{ - // This method should only be called with the range in proper order (start comes before end). - ASDisplayNodeAssert(ASIndexPathEqualToIndexPath(ASIndexPathMinimum(range.start, range.end), range.start), @"flowLayoutDistanceForRange: called with invalid range"); - - if (ASIndexPathEqualToIndexPath(range.start, range.end)) { - return 0; - } - - NSInteger totalRowCount = 0; - NSUInteger numberOfRowsInSection = 0; - NSArray *completedNodes = [_dataSource completedNodes]; - - for (NSInteger section = range.start.section; section <= range.end.section; section++) { - numberOfRowsInSection = [(NSArray *)completedNodes[section] count]; - totalRowCount += numberOfRowsInSection; - - if (section == range.start.section) { - // For the start section, make sure we don't count the rows before the start row. - totalRowCount -= range.start.row; - } else if (section == range.end.section) { - // For the start section, make sure we don't count the rows after the end row. - totalRowCount -= (numberOfRowsInSection - (range.end.row + 1)); - } - } - - ASDisplayNodeAssert(totalRowCount >= 0, @"totalRowCount in flowLayoutDistanceForRange: should not be negative"); - return totalRowCount; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Private/ASDataController+Subclasses.h b/submodules/AsyncDisplayKit/AsyncDisplayKit/Private/ASDataController+Subclasses.h deleted file mode 100644 index 8061e25a8d..0000000000 --- a/submodules/AsyncDisplayKit/AsyncDisplayKit/Private/ASDataController+Subclasses.h +++ /dev/null @@ -1,181 +0,0 @@ -// -// ASDataController+Subclasses.h -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#ifndef MINIMAL_ASDK - -#pragma once -#import - -@class ASIndexedNodeContext; - -typedef void (^ASDataControllerCompletionBlock)(NSArray *nodes, NSArray *indexPaths); - -@interface ASDataController (Subclasses) - -#pragma mark - Internal editing & completed store querying - -/** - * Read-only access to the underlying editing nodes of the given kind - */ -- (NSMutableArray *)editingNodesOfKind:(NSString *)kind; - -/** - * Read only access to the underlying completed nodes of the given kind - */ -- (NSMutableArray *)completedNodesOfKind:(NSString *)kind; - -#pragma mark - Node sizing - -/** - * Measure and layout the given nodes in optimized batches, constraining each to a given size in `constrainedSizeForNodeOfKind:atIndexPath:`. - * - * This method runs synchronously. - * @param batchCompletion A handler to be run after each batch is completed. It is executed synchronously on the calling thread. - */ -- (void)batchLayoutNodesFromContexts:(NSArray *)contexts batchCompletion:(ASDataControllerCompletionBlock)batchCompletionHandler; - -/** - * Provides the size range for a specific node during the layout process. - */ -- (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; - -#pragma mark - Node & Section Insertion/Deletion API - -/** - * Inserts the given nodes of the specified kind into the backing store, calling completion on the main thread when the write finishes. - */ -- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(ASDataControllerCompletionBlock)completionBlock; - -/** - * Deletes the given nodes of the specified kind in the backing store, calling completion on the main thread when the deletion finishes. - */ -- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(ASDataControllerCompletionBlock)completionBlock; - -/** - * Inserts the given sections of the specified kind in the backing store, calling completion on the main thread when finished. - */ -- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock; - -/** - * Deletes the given sections in the backing store, calling completion on the main thread when finished. - */ -- (void)deleteSections:(NSIndexSet *)indexSet completion:(void (^)())completionBlock; - -#pragma mark - Data Manipulation Hooks - -/** - * Notifies the subclass to perform any work needed before the data controller is reloaded entirely - * - * @discussion This method will be performed before the data controller enters its editing queue. - * The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or - * data stores before entering into editing the backing store on a background thread. - */ - - (void)prepareForReloadDataWithSectionCount:(NSInteger)newSectionCount; - -/** - * Notifies the subclass that the data controller is about to reload its data entirely - * - * @discussion This method will be performed on the data controller's editing background queue before the parent's - * concrete implementation. This is a great place to perform new node creation like supplementary views - * or header/footer nodes. - */ -- (void)willReloadDataWithSectionCount:(NSInteger)newSectionCount; - -/** - * Notifies the subclass to perform setup before sections are inserted in the data controller - * - * @discussion This method will be performed before the data controller enters its editing queue. - * The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or - * data stores before entering into editing the backing store on a background thread. - * - * @param sections Indices of sections to be inserted - */ -- (void)prepareForInsertSections:(NSIndexSet *)sections; - -/** - * Notifies the subclass that the data controller will insert new sections at the given position - * - * @discussion This method will be performed on the data controller's editing background queue before the parent's - * concrete implementation. This is a great place to perform any additional transformations like supplementary views - * or header/footer nodes. - * - * @param sections Indices of sections to be inserted - */ -- (void)willInsertSections:(NSIndexSet *)sections; - -/** - * Notifies the subclass to perform setup before sections are deleted in the data controller - * - * @discussion This method will be performed before the data controller enters its editing queue. - * The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or - * data stores before entering into editing the backing store on a background thread. - * - * @param sections Indices of sections to be inserted - */ -- (void)prepareForDeleteSections:(NSIndexSet *)sections; - -/** - * Notifies the subclass that the data controller will delete sections at the given positions - * - * @discussion This method will be performed on the data controller's editing background queue before the parent's - * concrete implementation. This is a great place to perform any additional transformations like supplementary views - * or header/footer nodes. - * - * @param sections Indices of sections to be deleted - */ -- (void)willDeleteSections:(NSIndexSet *)sections; - -/** - * Notifies the subclass to perform setup before rows are inserted in the data controller. - * - * @discussion This method will be performed before the data controller enters its editing queue. - * The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or - * data stores before entering into editing the backing store on a background thread. - * - * @param indexPaths Index paths for the rows to be inserted. - */ -- (void)prepareForInsertRowsAtIndexPaths:(NSArray *)indexPaths; - -/** - * Notifies the subclass that the data controller will insert new rows at the given index paths. - * - * @discussion This method will be performed on the data controller's editing background queue before the parent's - * concrete implementation. This is a great place to perform any additional transformations like supplementary views - * or header/footer nodes. - * - * @param indexPaths Index paths for the rows to be inserted. - */ -- (void)willInsertRowsAtIndexPaths:(NSArray *)indexPaths; - -/** - * Notifies the subclass to perform setup before rows are deleted in the data controller. - * - * @discussion This method will be performed before the data controller enters its editing queue. - * The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or - * data stores before entering into editing the backing store on a background thread. - * - * @param indexPaths Index paths for the rows to be deleted. - */ -- (void)prepareForDeleteRowsAtIndexPaths:(NSArray *)indexPaths; - -/** - * Notifies the subclass that the data controller will delete rows at the given index paths. - * - * @discussion This method will be performed before the data controller enters its editing queue. - * The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or - * data stores before entering into editing the backing store on a background thread. - * - * @param indexPaths Index paths for the rows to be deleted. - */ -- (void)willDeleteRowsAtIndexPaths:(NSArray *)indexPaths; - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/BUCK b/submodules/AsyncDisplayKit/BUCK index 50488256ca..e4a512cf8e 100644 --- a/submodules/AsyncDisplayKit/BUCK +++ b/submodules/AsyncDisplayKit/BUCK @@ -1,30 +1,21 @@ load("//Config:buck_rule_macros.bzl", "framework") -ASYNCDISPLAYKIT_EXPORTED_HEADERS = glob([ - "Source/*.h", - "Source/Details/**/*.h", - "Source/Layout/*.h", - "Source/Base/*.h", - "Source/Debug/AsyncDisplayKit+Debug.h", - "Source/TextKit/ASTextNodeTypes.h", - "Source/TextKit/ASTextKitComponents.h" +public_headers = glob([ + "Source/PublicHeaders/AsyncDisplayKit/*.h", ]) -ASYNCDISPLAYKIT_PRIVATE_HEADERS = glob([ - "Source/**/*.h" - ], - exclude = ASYNCDISPLAYKIT_EXPORTED_HEADERS, -) +private_headers = glob([ + "Source/*.h", +]) framework( name = "AsyncDisplayKit", - headers = ASYNCDISPLAYKIT_PRIVATE_HEADERS, - exported_headers = ASYNCDISPLAYKIT_EXPORTED_HEADERS, srcs = glob([ "Source/**/*.m", "Source/**/*.mm", - "Source/Base/*.m" ]), + headers = private_headers, + exported_headers = public_headers, compiler_flags = [ "-DMINIMAL_ASDK", ], diff --git a/submodules/AsyncDisplayKit/BUILD b/submodules/AsyncDisplayKit/BUILD new file mode 100644 index 0000000000..6d054283b8 --- /dev/null +++ b/submodules/AsyncDisplayKit/BUILD @@ -0,0 +1,35 @@ +public_headers = glob([ + "Source/PublicHeaders/AsyncDisplayKit/*.h", +]) + +private_headers = glob([ + "Source/*.h", +]) + +objc_library( + name = "AsyncDisplayKit", + enable_modules = True, + module_name = "AsyncDisplayKit", + srcs = glob([ + "Source/**/*.m", + "Source/**/*.mm", + ]) + private_headers, + hdrs = public_headers, + defines = [ + "MINIMAL_ASDK", + ], + includes = [ + "Source/PublicHeaders", + ], + sdk_frameworks = [ + "Foundation", + "UIKit", + "QuartzCore", + "CoreMedia", + "CoreText", + "CoreGraphics", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/AsyncDisplayKit/CHANGELOG.md b/submodules/AsyncDisplayKit/CHANGELOG.md deleted file mode 100644 index 0cddbfab45..0000000000 --- a/submodules/AsyncDisplayKit/CHANGELOG.md +++ /dev/null @@ -1,685 +0,0 @@ -# Change Log - -## [2.8](https://github.com/TextureGroup/Texture/tree/2.8) (2019-02-12) -[Full Changelog](https://github.com/TextureGroup/Texture/compare/2.7...2.8) - -**Merged pull requests:** - -- Remove duplicate definition of category "YogaDebugging" [\#1331](https://github.com/TextureGroup/Texture/pull/1331) ([nguyenhuy](https://github.com/nguyenhuy)) -- Add Yoga layout to ASDKGram Texture cells [\#1315](https://github.com/TextureGroup/Texture/pull/1315) ([maicki](https://github.com/maicki)) -- Remove let and var macros now that we're all-C++ [\#1312](https://github.com/TextureGroup/Texture/pull/1312) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Add experiments to skip waiting for updates of collection and table views [\#1311](https://github.com/TextureGroup/Texture/pull/1311) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[ASCollectionView\] Supplementary nodes should not enter ASHierarchyStateRangeManaged. [\#1310](https://github.com/TextureGroup/Texture/pull/1310) ([appleguy](https://github.com/appleguy)) -- Fix deprecated implementations warning [\#1306](https://github.com/TextureGroup/Texture/pull/1306) ([maicki](https://github.com/maicki)) -- Improve separation of code for layout method types [\#1305](https://github.com/TextureGroup/Texture/pull/1305) ([maicki](https://github.com/maicki)) -- Fix loading items in ASDKGram IGListKit tab [\#1300](https://github.com/TextureGroup/Texture/pull/1300) ([maicki](https://github.com/maicki)) -- performance spell correction [\#1298](https://github.com/TextureGroup/Texture/pull/1298) ([wxyong](https://github.com/wxyong)) -- Add some snapshot tests for ASTextNode2 truncation modes. [\#1296](https://github.com/TextureGroup/Texture/pull/1296) ([wiseoldduck](https://github.com/wiseoldduck)) -- Reduce startup time. [\#1294](https://github.com/TextureGroup/Texture/pull/1294) ([dmaclach](https://github.com/dmaclach)) -- Reduce startup time. [\#1293](https://github.com/TextureGroup/Texture/pull/1293) ([dmaclach](https://github.com/dmaclach)) -- Reduce startup time. [\#1292](https://github.com/TextureGroup/Texture/pull/1292) ([dmaclach](https://github.com/dmaclach)) -- Reduce startup time. [\#1291](https://github.com/TextureGroup/Texture/pull/1291) ([dmaclach](https://github.com/dmaclach)) -- Reduce startup time. [\#1288](https://github.com/TextureGroup/Texture/pull/1288) ([dmaclach](https://github.com/dmaclach)) -- Add a way to opt out of always-clear-data behavior in ASCollectionView and ASTableView [\#1284](https://github.com/TextureGroup/Texture/pull/1284) ([nguyenhuy](https://github.com/nguyenhuy)) -- Copy yogaChildren in accessor method. Avoid using accessor method internally [\#1283](https://github.com/TextureGroup/Texture/pull/1283) ([maicki](https://github.com/maicki)) -- Use cell mode while wrapping supplementary nodes [\#1282](https://github.com/TextureGroup/Texture/pull/1282) ([maicki](https://github.com/maicki)) -- Access thread safe property to avoid assertion [\#1281](https://github.com/TextureGroup/Texture/pull/1281) ([wiseoldduck](https://github.com/wiseoldduck)) -- Match AS\_USE\_VIDEO usage in tests to definitions [\#1280](https://github.com/TextureGroup/Texture/pull/1280) ([wiseoldduck](https://github.com/wiseoldduck)) -- Update test imports to use framework import [\#1279](https://github.com/TextureGroup/Texture/pull/1279) ([maicki](https://github.com/maicki)) -- Set automaticallyAdjustsContentOffset to ASTableView when view is load [\#1278](https://github.com/TextureGroup/Texture/pull/1278) ([strangeliu](https://github.com/strangeliu)) -- Remove UIKit header import in AsyncTransaction file [\#1275](https://github.com/TextureGroup/Texture/pull/1275) ([zhongwuzw](https://github.com/zhongwuzw)) -- Disable a11y cache [\#1274](https://github.com/TextureGroup/Texture/pull/1274) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- Introduce ASCellLayoutMode [\#1273](https://github.com/TextureGroup/Texture/pull/1273) ([maicki](https://github.com/maicki)) -- During yoga layout, escalate directly to yoga root rather than walking up [\#1269](https://github.com/TextureGroup/Texture/pull/1269) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Forward hitTest:withEvent and piontInside:withEvent: to node within \_ASCollectionViewCell [\#1268](https://github.com/TextureGroup/Texture/pull/1268) ([maicki](https://github.com/maicki)) -- Wrap supplementary node blocks to enable resizing them. [\#1265](https://github.com/TextureGroup/Texture/pull/1265) ([wiseoldduck](https://github.com/wiseoldduck)) -- Move Bluebird to new row. [\#1264](https://github.com/TextureGroup/Texture/pull/1264) ([ay8s](https://github.com/ay8s)) -- Added Bluebird [\#1263](https://github.com/TextureGroup/Texture/pull/1263) ([ShihabM](https://github.com/ShihabM)) -- Move assertions so they are valid. [\#1261](https://github.com/TextureGroup/Texture/pull/1261) ([wiseoldduck](https://github.com/wiseoldduck)) -- Fix isTruncated logic in ASTextNode2 [\#1259](https://github.com/TextureGroup/Texture/pull/1259) ([maicki](https://github.com/maicki)) -- Documentation typo, "trying" written two times [\#1258](https://github.com/TextureGroup/Texture/pull/1258) ([tataevr](https://github.com/tataevr)) -- \[ASPrimitiveTraitCollection\] Fix ASPrimitiveTraitCollectionMakeDefault and implement containerSize [\#1256](https://github.com/TextureGroup/Texture/pull/1256) ([rcancro](https://github.com/rcancro)) -- Yoga debug info [\#1253](https://github.com/TextureGroup/Texture/pull/1253) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- Avoid using global Mutex variables [\#1252](https://github.com/TextureGroup/Texture/pull/1252) ([nguyenhuy](https://github.com/nguyenhuy)) -- Allow setting build.sh SDK and platform w/ env variables [\#1249](https://github.com/TextureGroup/Texture/pull/1249) ([wiseoldduck](https://github.com/wiseoldduck)) -- add more delegate methods for monitoring network image node progress [\#1247](https://github.com/TextureGroup/Texture/pull/1247) ([ernestmama](https://github.com/ernestmama)) -- Start a thrash test suite for the collection node [\#1246](https://github.com/TextureGroup/Texture/pull/1246) ([mikezucc](https://github.com/mikezucc)) -- Add development docs structure [\#1245](https://github.com/TextureGroup/Texture/pull/1245) ([garrettmoon](https://github.com/garrettmoon)) -- Convert YGUndefined back to CGFLOAT\_MAX for Texture layout [\#1244](https://github.com/TextureGroup/Texture/pull/1244) ([tnorman42](https://github.com/tnorman42)) -- Add way to compile out ASTextNode + TextKit dependencies [\#1242](https://github.com/TextureGroup/Texture/pull/1242) ([maicki](https://github.com/maicki)) -- Add AS\_USE\_VIDEO flag and subspec for Video [\#1240](https://github.com/TextureGroup/Texture/pull/1240) ([maicki](https://github.com/maicki)) -- Releases/p6.78 [\#1236](https://github.com/TextureGroup/Texture/pull/1236) ([ernestmama](https://github.com/ernestmama)) -- \[ASDisplayNode\] Propagate traits before loading a subnode [\#1234](https://github.com/TextureGroup/Texture/pull/1234) ([rcancro](https://github.com/rcancro)) -- Correct some block self references to strongSelf [\#1231](https://github.com/TextureGroup/Texture/pull/1231) ([wiseoldduck](https://github.com/wiseoldduck)) -- Update image-node.md [\#1230](https://github.com/TextureGroup/Texture/pull/1230) ([orkhan-huseynov](https://github.com/orkhan-huseynov)) -- Have node and controller share lock [\#1227](https://github.com/TextureGroup/Texture/pull/1227) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Initialize mutex assertion variables [\#1226](https://github.com/TextureGroup/Texture/pull/1226) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Remove CHECK\_LOCKING\_SAFETY check [\#1225](https://github.com/TextureGroup/Texture/pull/1225) ([maicki](https://github.com/maicki)) -- Clean up our mutex, fix try\_lock not hooking into assert mechanism [\#1219](https://github.com/TextureGroup/Texture/pull/1219) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Fix warning using \_\_builtin\_popcount [\#1218](https://github.com/TextureGroup/Texture/pull/1218) ([maicki](https://github.com/maicki)) -- Fix A11Y for horizontal collection nodes in Texture [\#1217](https://github.com/TextureGroup/Texture/pull/1217) ([maicki](https://github.com/maicki)) -- ASCATransactionQueue interface trashing improvements [\#1216](https://github.com/TextureGroup/Texture/pull/1216) ([maicki](https://github.com/maicki)) -- Fix shouldTruncateForConstrainedSize in ASTextNode2 [\#1214](https://github.com/TextureGroup/Texture/pull/1214) ([maicki](https://github.com/maicki)) -- ASThread: Remove Locker, Unlocker, and SharedMutex [\#1213](https://github.com/TextureGroup/Texture/pull/1213) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Cleanup Dangerfile [\#1212](https://github.com/TextureGroup/Texture/pull/1212) ([nguyenhuy](https://github.com/nguyenhuy)) -- Rework ASTraitCollection to Fix Warnings and Remove Boilerplate [\#1211](https://github.com/TextureGroup/Texture/pull/1211) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Add -Wno-implicit-retain-self to podspec + smaller cleanups \#trivial [\#1209](https://github.com/TextureGroup/Texture/pull/1209) ([maicki](https://github.com/maicki)) -- Address compiler warnings \#trivial [\#1207](https://github.com/TextureGroup/Texture/pull/1207) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Convert the codebase to Objective-C++ [\#1206](https://github.com/TextureGroup/Texture/pull/1206) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Add tests for accessibility [\#1205](https://github.com/TextureGroup/Texture/pull/1205) ([wiseoldduck](https://github.com/wiseoldduck)) -- Revert \#1023 \#trivial [\#1204](https://github.com/TextureGroup/Texture/pull/1204) ([maicki](https://github.com/maicki)) -- Follow up cleanup \#trivial [\#1203](https://github.com/TextureGroup/Texture/pull/1203) ([maicki](https://github.com/maicki)) -- Add experiment flag to skip layoutIfNeeded in enterPreloadState for ASM nodes \#trivial [\#1201](https://github.com/TextureGroup/Texture/pull/1201) ([maicki](https://github.com/maicki)) -- Fix logic cleaning data if delegate / dataSource changes and bring over logic to ASTableView [\#1200](https://github.com/TextureGroup/Texture/pull/1200) ([maicki](https://github.com/maicki)) -- Tweak a11y label aggregation behavior to enable container label overrides [\#1199](https://github.com/TextureGroup/Texture/pull/1199) ([maicki](https://github.com/maicki)) -- Fix shadowed var warning \(and add clarity\) \#trivial [\#1198](https://github.com/TextureGroup/Texture/pull/1198) ([wiseoldduck](https://github.com/wiseoldduck)) -- Allow configuring imageCache when initializing ASPINRemoteImageDownloader. [\#1197](https://github.com/TextureGroup/Texture/pull/1197) ([wiseoldduck](https://github.com/wiseoldduck)) -- ASTextNode2 to consider both width and height when determining if it is calculating an intrinsic size [\#1196](https://github.com/TextureGroup/Texture/pull/1196) ([ernestmama](https://github.com/ernestmama)) -- Remove extraneous ";" \#trivial [\#1194](https://github.com/TextureGroup/Texture/pull/1194) ([wiseoldduck](https://github.com/wiseoldduck)) -- Newline character support and truncated line sizing improvement. [\#1193](https://github.com/TextureGroup/Texture/pull/1193) ([wiseoldduck](https://github.com/wiseoldduck)) -- Correct linePositionModifier behavior [\#1192](https://github.com/TextureGroup/Texture/pull/1192) ([maicki](https://github.com/maicki)) -- Move AS\_TEXT\_ALERT\_UNIMPLEMENTED\_FEATURE into ASTextNodeCommon \#trivial [\#1191](https://github.com/TextureGroup/Texture/pull/1191) ([maicki](https://github.com/maicki)) -- A11y for scrollnode [\#1188](https://github.com/TextureGroup/Texture/pull/1188) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- Yoga integration improvements [\#1187](https://github.com/TextureGroup/Texture/pull/1187) ([maicki](https://github.com/maicki)) -- Remove unnecessary ASWeakProxy import \#trivial [\#1186](https://github.com/TextureGroup/Texture/pull/1186) ([maicki](https://github.com/maicki)) -- Directly use \_\_instanceLock\_\_ to lock / unlock without having to create and destroy a MutextUnlocker \#trivial [\#1185](https://github.com/TextureGroup/Texture/pull/1185) ([maicki](https://github.com/maicki)) -- Don’t handle touches on additional attributed message if passthrough is enabled [\#1184](https://github.com/TextureGroup/Texture/pull/1184) ([maicki](https://github.com/maicki)) -- Set the default values for showsVerticalScrollIndicator and showsHorizontalScrollIndicator \#trivial [\#1181](https://github.com/TextureGroup/Texture/pull/1181) ([maicki](https://github.com/maicki)) -- Move import of stdatomic to ASRecursiveUnfairLock implementation file \#trivial [\#1180](https://github.com/TextureGroup/Texture/pull/1180) ([maicki](https://github.com/maicki)) -- Add NSLocking conformance to ASNodeController [\#1179](https://github.com/TextureGroup/Texture/pull/1179) ([maicki](https://github.com/maicki)) -- Only initialize framework once, avoid multiple across tests \#trivial [\#1178](https://github.com/TextureGroup/Texture/pull/1178) ([maicki](https://github.com/maicki)) -- Expose a way to determine if a text node will truncate for a given constrained size \#trivial [\#1177](https://github.com/TextureGroup/Texture/pull/1177) ([maicki](https://github.com/maicki)) -- Fix define spaces \#trivial [\#1176](https://github.com/TextureGroup/Texture/pull/1176) ([maicki](https://github.com/maicki)) -- Expose test\_resetWithConfiguration: for testing \#trivial [\#1175](https://github.com/TextureGroup/Texture/pull/1175) ([maicki](https://github.com/maicki)) -- Add way to suppress invalid CollectionUpdateExceptions \#trivial [\#1173](https://github.com/TextureGroup/Texture/pull/1173) ([maicki](https://github.com/maicki)) -- Use interface state to manage image loading \#trivial [\#1172](https://github.com/TextureGroup/Texture/pull/1172) ([maicki](https://github.com/maicki)) -- ASTableNode init method match checks from ASCollectionNode [\#1171](https://github.com/TextureGroup/Texture/pull/1171) ([maicki](https://github.com/maicki)) -- \[ASDisplayNode\] Expose default Texture-set accessibility values as properties [\#1170](https://github.com/TextureGroup/Texture/pull/1170) ([jiawernlim](https://github.com/jiawernlim)) -- Fix mismatch in UIAccessibilityAction selector method [\#1169](https://github.com/TextureGroup/Texture/pull/1169) ([maicki](https://github.com/maicki)) -- Small fix in ASTextKitRenderer \#trivial [\#1167](https://github.com/TextureGroup/Texture/pull/1167) ([nguyenhuy](https://github.com/nguyenhuy)) -- ASTextNode2 to ignore certain text alignments while calculating intrinsic size [\#1166](https://github.com/TextureGroup/Texture/pull/1166) ([nguyenhuy](https://github.com/nguyenhuy)) -- Update Jekyll to 3.6.3 [\#1165](https://github.com/TextureGroup/Texture/pull/1165) ([nguyenhuy](https://github.com/nguyenhuy)) -- Migrate placeholder example project from 1.0 to 2.x [\#1164](https://github.com/TextureGroup/Texture/pull/1164) ([ay8s](https://github.com/ay8s)) -- Update documentation of ASNetworkImageNodeDelegate \#trivial [\#1163](https://github.com/TextureGroup/Texture/pull/1163) ([nguyenhuy](https://github.com/nguyenhuy)) -- Make ASEditableTextNode accessible to VoiceOver [\#1162](https://github.com/TextureGroup/Texture/pull/1162) ([ay8s](https://github.com/ay8s)) -- Mismatch name experimental features [\#1159](https://github.com/TextureGroup/Texture/pull/1159) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- Set default tuning params [\#1158](https://github.com/TextureGroup/Texture/pull/1158) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- Clean up timing of layout tree flattening/ copying of unflattened tree for Weaver [\#1157](https://github.com/TextureGroup/Texture/pull/1157) ([mikezucc](https://github.com/mikezucc)) -- Only clear ASCollectionView's data during deallocation [\#1154](https://github.com/TextureGroup/Texture/pull/1154) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[ASTextNode2\] Add improved support for all line-break modes in experimental text node. [\#1150](https://github.com/TextureGroup/Texture/pull/1150) ([wiseoldduck](https://github.com/wiseoldduck)) -- \[ASImageNode\] Fix a threading issue which can cause a display completion block to never be executed [\#1148](https://github.com/TextureGroup/Texture/pull/1148) ([nguyenhuy](https://github.com/nguyenhuy)) -- Guard photo library with macro for tests [\#1147](https://github.com/TextureGroup/Texture/pull/1147) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- Rollout ASDeallocQueueV2 \#trivial [\#1143](https://github.com/TextureGroup/Texture/pull/1143) ([ernestmama](https://github.com/ernestmama)) -- Fix crash setting attributed text on multiple threads [\#1141](https://github.com/TextureGroup/Texture/pull/1141) ([maicki](https://github.com/maicki)) -- Add missing NS\_NOESCAPE attributes in overwritten methods \#trivial [\#1139](https://github.com/TextureGroup/Texture/pull/1139) ([ejensen](https://github.com/ejensen)) -- Add missing comma in ASExperimentalFeatures \#trivial [\#1137](https://github.com/TextureGroup/Texture/pull/1137) ([nguyenhuy](https://github.com/nguyenhuy)) -- Add ASExperimentalSkipClearData \#trivial [\#1136](https://github.com/TextureGroup/Texture/pull/1136) ([maicki](https://github.com/maicki)) -- Fix RemoteImageDownloader name mismatch \#trivial [\#1134](https://github.com/TextureGroup/Texture/pull/1134) ([ernestmama](https://github.com/ernestmama)) -- Fix compilation warnings \#trivial [\#1132](https://github.com/TextureGroup/Texture/pull/1132) ([ejensen](https://github.com/ejensen)) -- Remove reliance on shared\_ptr for ASDisplayNodeLayouts [\#1131](https://github.com/TextureGroup/Texture/pull/1131) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Make yoga & layout specs faster by eliminating some copies [\#1128](https://github.com/TextureGroup/Texture/pull/1128) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Remove ASRectMap, which is not worth its own weight [\#1127](https://github.com/TextureGroup/Texture/pull/1127) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[ASPINRemoteImageDownloader\] Fix +setSharedPreconfiguredRemoteImageManager:'s doc \#trivial [\#1126](https://github.com/TextureGroup/Texture/pull/1126) ([nguyenhuy](https://github.com/nguyenhuy)) -- Add a method for setting preconfigured PINRemoteImageManager [\#1124](https://github.com/TextureGroup/Texture/pull/1124) ([ernestmama](https://github.com/ernestmama)) -- Don't copy onDidLoadBlocks \#trivial [\#1123](https://github.com/TextureGroup/Texture/pull/1123) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Remove use of NSHashTable for interface state delegates \#trivial [\#1122](https://github.com/TextureGroup/Texture/pull/1122) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Fix typos and minor code cleanups \#trivial [\#1120](https://github.com/TextureGroup/Texture/pull/1120) ([nguyenhuy](https://github.com/nguyenhuy)) -- Don't setNeedsDisplay on text node 2 measure \#trivial [\#1116](https://github.com/TextureGroup/Texture/pull/1116) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Don't copy container during ASTextNode2 measure [\#1115](https://github.com/TextureGroup/Texture/pull/1115) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Make interface state delegate non optional [\#1112](https://github.com/TextureGroup/Texture/pull/1112) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- Interface state not update correctly during layer thrash. [\#1111](https://github.com/TextureGroup/Texture/pull/1111) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- Fix layer backed nodes not update properly [\#1110](https://github.com/TextureGroup/Texture/pull/1110) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- changelog fix: let / var macros did not make it to 2.7 [\#1109](https://github.com/TextureGroup/Texture/pull/1109) ([jozsefmihalicza](https://github.com/jozsefmihalicza)) -- Improve locking around clearContents [\#1107](https://github.com/TextureGroup/Texture/pull/1107) ([maicki](https://github.com/maicki)) -- Add missing argument for calling image download completion block \#trivial [\#1106](https://github.com/TextureGroup/Texture/pull/1106) ([maicki](https://github.com/maicki)) -- Fix URL for blog about Pinterest [\#1105](https://github.com/TextureGroup/Texture/pull/1105) ([muukii](https://github.com/muukii)) -- Remove necessity to use view to access rangeController in ASTableNode, ASCollectionNode [\#1103](https://github.com/TextureGroup/Texture/pull/1103) ([maicki](https://github.com/maicki)) -- Add a -textureDidInitialize delegate callback [\#1100](https://github.com/TextureGroup/Texture/pull/1100) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Reuse interface state delegates when calling out \#trivial [\#1099](https://github.com/TextureGroup/Texture/pull/1099) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Add an explicit cast to satisfy strict compilers \#trivial [\#1098](https://github.com/TextureGroup/Texture/pull/1098) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Fix a couple typos. [\#1092](https://github.com/TextureGroup/Texture/pull/1092) ([jtbthethird](https://github.com/jtbthethird)) -- \#trivial Shouldn't hold the lock while adding subnodes [\#1091](https://github.com/TextureGroup/Texture/pull/1091) ([garrettmoon](https://github.com/garrettmoon)) -- Allow to add interface state delegate in background. [\#1090](https://github.com/TextureGroup/Texture/pull/1090) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- Fix Typo [\#1089](https://github.com/TextureGroup/Texture/pull/1089) ([jtbthethird](https://github.com/jtbthethird)) -- Add subnode should not be called with the lock held. \#trivial [\#1088](https://github.com/TextureGroup/Texture/pull/1088) ([garrettmoon](https://github.com/garrettmoon)) -- Unlock before cleanup and calling out to subclass hooks for animated images. [\#1087](https://github.com/TextureGroup/Texture/pull/1087) ([maicki](https://github.com/maicki)) -- Fix collection editing [\#1081](https://github.com/TextureGroup/Texture/pull/1081) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- Fix compiler error in ASLocking \#trivial [\#1079](https://github.com/TextureGroup/Texture/pull/1079) ([nguyenhuy](https://github.com/nguyenhuy)) -- Update showcase to add Wishpoke [\#1078](https://github.com/TextureGroup/Texture/pull/1078) ([dhatuna](https://github.com/dhatuna)) -- \[License\] Simplify the Texture license to be pure Apache 2 \(removing ASDK-Licenses\). [\#1077](https://github.com/TextureGroup/Texture/pull/1077) ([appleguy](https://github.com/appleguy)) -- Fix multiple documentation issues \#trivial [\#1073](https://github.com/TextureGroup/Texture/pull/1073) ([maicki](https://github.com/maicki)) -- Refactored `accessibleElements` to `accessibilityElements` [\#1069](https://github.com/TextureGroup/Texture/pull/1069) ([jiawernlim](https://github.com/jiawernlim)) -- Readability improvements in ASDataController \#trivial [\#1067](https://github.com/TextureGroup/Texture/pull/1067) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Remove direct ivar access on non-self object to fix mocking case \#trivial [\#1066](https://github.com/TextureGroup/Texture/pull/1066) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Reduce copying in ASTextNode2 stack [\#1065](https://github.com/TextureGroup/Texture/pull/1065) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Add an experimental framesetter cache in ASTextNode2 [\#1063](https://github.com/TextureGroup/Texture/pull/1063) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Remove extra string/attributed string creation in accessibility props [\#1062](https://github.com/TextureGroup/Texture/pull/1062) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Remove objc association & weak proxy from node -\> controller pointer [\#1061](https://github.com/TextureGroup/Texture/pull/1061) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Remove CATransaction signposts [\#1060](https://github.com/TextureGroup/Texture/pull/1060) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[ASTextNode2\] Simplify allocWithZone: + initialize implementation \#trivial [\#1059](https://github.com/TextureGroup/Texture/pull/1059) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[ASTextNode\] Fixes in ASTextKitFontSizeAdjuster [\#1056](https://github.com/TextureGroup/Texture/pull/1056) ([ejensen](https://github.com/ejensen)) -- Revert "Optimize drawing code + add examples how to round corners \(\#996\) [\#1055](https://github.com/TextureGroup/Texture/pull/1055) ([maicki](https://github.com/maicki)) -- Add NS\_DESIGNATED\_INITIALIZER to ASViewController initWithNode: [\#1054](https://github.com/TextureGroup/Texture/pull/1054) ([maicki](https://github.com/maicki)) -- Fix headers in markdown [\#1053](https://github.com/TextureGroup/Texture/pull/1053) ([Un3qual](https://github.com/Un3qual)) -- Avoid setting frame on a node's backing store while holding its lock [\#1048](https://github.com/TextureGroup/Texture/pull/1048) ([nguyenhuy](https://github.com/nguyenhuy)) -- \#trivial Add a comment about tiling mode and issue \#1046 [\#1047](https://github.com/TextureGroup/Texture/pull/1047) ([wiseoldduck](https://github.com/wiseoldduck)) -- Add documentation for rounding corners within Texture \#trivial [\#1044](https://github.com/TextureGroup/Texture/pull/1044) ([maicki](https://github.com/maicki)) -- Improve locking situation in ASVideoPlayerNode [\#1042](https://github.com/TextureGroup/Texture/pull/1042) ([maicki](https://github.com/maicki)) -- Revert unreleased layout debug method name change from \#1030 \#trivial [\#1039](https://github.com/TextureGroup/Texture/pull/1039) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Pin OCMock version to 3.4.1 because 3.4.2 has issues [\#1038](https://github.com/TextureGroup/Texture/pull/1038) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Fix & update ASCollectionNode constrained size doc. \#trivial [\#1037](https://github.com/TextureGroup/Texture/pull/1037) ([ay8s](https://github.com/ay8s)) -- Fix warning for ASLayout method override for the designated initializer of the superclass '-init' not found \#trivial [\#1036](https://github.com/TextureGroup/Texture/pull/1036) ([maicki](https://github.com/maicki)) -- Fix the bug I introduced in \#1030 \#trivial [\#1035](https://github.com/TextureGroup/Texture/pull/1035) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Turn off exceptions to reduce binary size \(-600KB for arm64\) [\#1033](https://github.com/TextureGroup/Texture/pull/1033) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Turn lock-checking on only when assertions are enabled \#trivial [\#1032](https://github.com/TextureGroup/Texture/pull/1032) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Remove NSMutableArray for retaining sublayout elements [\#1030](https://github.com/TextureGroup/Texture/pull/1030) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Create and set delegate for clip corner layers within ASDisplayNode [\#1029](https://github.com/TextureGroup/Texture/pull/1029) ([maicki](https://github.com/maicki)) -- Split framework dependencies into separate subspecs [\#1028](https://github.com/TextureGroup/Texture/pull/1028) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Remove misleading comment and add assertion \#trivial [\#1027](https://github.com/TextureGroup/Texture/pull/1027) ([wiseoldduck](https://github.com/wiseoldduck)) -- Address warnings in Xcode \>= 9.3 about using %zd for NSInteger \#trivial [\#1026](https://github.com/TextureGroup/Texture/pull/1026) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Fix 32-bit simulator build on Xcode \>= 9.3 [\#1025](https://github.com/TextureGroup/Texture/pull/1025) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Stricter locking assertions [\#1024](https://github.com/TextureGroup/Texture/pull/1024) ([nguyenhuy](https://github.com/nguyenhuy)) -- Make sure -\_completePendingLayoutTransition is called without the node's instance lock \#trivial [\#1023](https://github.com/TextureGroup/Texture/pull/1023) ([nguyenhuy](https://github.com/nguyenhuy)) -- Fix misleading/scary stack trace shown when an assertion occurs during node measurement [\#1022](https://github.com/TextureGroup/Texture/pull/1022) ([nguyenhuy](https://github.com/nguyenhuy)) -- Add an introduction for ASCornerLayoutSpec in layout2-layoutspec-types.md \#trivial [\#1021](https://github.com/TextureGroup/Texture/pull/1021) ([huang-kun](https://github.com/huang-kun)) -- Add showsHorizontal\(Vertical\)ScrollIndicator property applying from pending state \#trivial [\#1016](https://github.com/TextureGroup/Texture/pull/1016) ([maicki](https://github.com/maicki)) -- \[IGListKit\] Adds missing UIScrollViewDelegate method to DataSource proxy [\#1015](https://github.com/TextureGroup/Texture/pull/1015) ([wannabehero](https://github.com/wannabehero)) -- Introduce let / var macros and some further cleanup [\#1012](https://github.com/TextureGroup/Texture/pull/1012) ([maicki](https://github.com/maicki)) -- Properly consider node for responder methods [\#1008](https://github.com/TextureGroup/Texture/pull/1008) ([maicki](https://github.com/maicki)) -- Background image load api [\#1007](https://github.com/TextureGroup/Texture/pull/1007) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- Add move detection and support to ASLayoutTransition [\#1006](https://github.com/TextureGroup/Texture/pull/1006) ([wiseoldduck](https://github.com/wiseoldduck)) -- Fix warnings and a memory leak \#trivial [\#1003](https://github.com/TextureGroup/Texture/pull/1003) ([maicki](https://github.com/maicki)) -- Rewrite Swift Example [\#1002](https://github.com/TextureGroup/Texture/pull/1002) ([maicki](https://github.com/maicki)) -- Remove yoga layout spec, which has been superseded [\#999](https://github.com/TextureGroup/Texture/pull/999) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Optimize drawing code + add examples how to round corners [\#996](https://github.com/TextureGroup/Texture/pull/996) ([maicki](https://github.com/maicki)) -- Fix typo in containers-asviewcontroller.md [\#989](https://github.com/TextureGroup/Texture/pull/989) ([muukii](https://github.com/muukii)) -- Create transfer-array method and use it [\#987](https://github.com/TextureGroup/Texture/pull/987) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Add missing instance variables in ASTextNode and warnings cleanup \#trivial [\#984](https://github.com/TextureGroup/Texture/pull/984) ([maicki](https://github.com/maicki)) -- Optimize layout flattening [\#982](https://github.com/TextureGroup/Texture/pull/982) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Changed lost images to existing one. \#trivial [\#981](https://github.com/TextureGroup/Texture/pull/981) ([tataevr](https://github.com/tataevr)) -- \[texturegroup.org\] Use valid link for Upgrade to 2.0 beta 1 page \#trivial [\#980](https://github.com/TextureGroup/Texture/pull/980) ([mikezucc](https://github.com/mikezucc)) -- Adds support for having multiple interface state delegates. [\#979](https://github.com/TextureGroup/Texture/pull/979) ([garrettmoon](https://github.com/garrettmoon)) -- Create an experiment to remove extra collection teardown step [\#975](https://github.com/TextureGroup/Texture/pull/975) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Remove unused/unneeded header macros [\#973](https://github.com/TextureGroup/Texture/pull/973) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Standardize "extern" decls on AS\_EXTERN [\#972](https://github.com/TextureGroup/Texture/pull/972) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- ASConfiguration version check only when have json dict [\#971](https://github.com/TextureGroup/Texture/pull/971) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- Pointer check [\#970](https://github.com/TextureGroup/Texture/pull/970) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- Reduce usage of autorelease pools [\#968](https://github.com/TextureGroup/Texture/pull/968) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Update showcase to include Apollo for Reddit [\#967](https://github.com/TextureGroup/Texture/pull/967) ([christianselig](https://github.com/christianselig)) -- Fix crash when call needsMainThreadDeallocation on NSProxy instances \#trivial [\#965](https://github.com/TextureGroup/Texture/pull/965) ([nguyenhuy](https://github.com/nguyenhuy)) -- Fix name typo \#trivial [\#963](https://github.com/TextureGroup/Texture/pull/963) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- Generalize the main thread ivar deallocation system [\#959](https://github.com/TextureGroup/Texture/pull/959) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Add support for acquiring multiple locks at once [\#958](https://github.com/TextureGroup/Texture/pull/958) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Clean up async transaction system a bit [\#955](https://github.com/TextureGroup/Texture/pull/955) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Added 'Waplog' to showcase. [\#953](https://github.com/TextureGroup/Texture/pull/953) ([malikkuru](https://github.com/malikkuru)) -- Make ASPerformMainThreadDeallocation visible in C [\#952](https://github.com/TextureGroup/Texture/pull/952) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Cut 2.7 release [\#949](https://github.com/TextureGroup/Texture/pull/949) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Fixed removing node from supernode after layout transition [\#937](https://github.com/TextureGroup/Texture/pull/937) ([atitovdev](https://github.com/atitovdev)) -- add ASTextNode2 snapshot test [\#935](https://github.com/TextureGroup/Texture/pull/935) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- \[ASTextNode\] One more check variables before calling delegate method \#trivial [\#922](https://github.com/TextureGroup/Texture/pull/922) ([Flatout73](https://github.com/Flatout73)) -- Assert node did load before did enter visible way 1 [\#886](https://github.com/TextureGroup/Texture/pull/886) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- Renew supplementary node on relayout [\#842](https://github.com/TextureGroup/Texture/pull/842) ([wsdwsd0829](https://github.com/wsdwsd0829)) - -## [2.7](https://github.com/TextureGroup/Texture/tree/2.7) (2018-05-29) -[Full Changelog](https://github.com/TextureGroup/Texture/compare/2.6...2.7) - -**Merged pull requests:** - -- Update AppIcon in showcase [\#946](https://github.com/TextureGroup/Texture/pull/946) ([muukii](https://github.com/muukii)) -- Update tip-1-nodeBlocks.md [\#943](https://github.com/TextureGroup/Texture/pull/943) ([sagarbhosale](https://github.com/sagarbhosale)) -- \[ASTableView\] Generate a new cell layout if existing ones are invalid [\#942](https://github.com/TextureGroup/Texture/pull/942) ([nguyenhuy](https://github.com/nguyenhuy)) -- Update to unsplash [\#938](https://github.com/TextureGroup/Texture/pull/938) ([garrettmoon](https://github.com/garrettmoon)) -- \[ASTextNode2\] Simplify compare-assign check & lock \_pointScaleFactors accessor \#trivial [\#934](https://github.com/TextureGroup/Texture/pull/934) ([appleguy](https://github.com/appleguy)) -- Create a new dealloc queue that is more efficient [\#931](https://github.com/TextureGroup/Texture/pull/931) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[ASImageNode+AnimatedImage\] Fix early return when animatedImage is nil in setAnimatedImage \#trivial [\#925](https://github.com/TextureGroup/Texture/pull/925) ([flovouin](https://github.com/flovouin)) -- Remove assert. fix \#878 \#914 [\#924](https://github.com/TextureGroup/Texture/pull/924) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- Always call out to delegate for experiments, whether enabled or not [\#923](https://github.com/TextureGroup/Texture/pull/923) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[ASTextNode2\] Upgrade lock safety by protecting all ivars \(including rarely-changed ones\). [\#918](https://github.com/TextureGroup/Texture/pull/918) ([appleguy](https://github.com/appleguy)) -- \[ASCollectionNode/ASTableNode\] Fix a crash occurs while remeasuring cell nodes [\#917](https://github.com/TextureGroup/Texture/pull/917) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[ASDisplayNode\] Improve thread-safety of didExitHierarchy \#trivial [\#916](https://github.com/TextureGroup/Texture/pull/916) ([nguyenhuy](https://github.com/nguyenhuy)) -- Prevent UITextView from updating contentOffset while deallocating [\#915](https://github.com/TextureGroup/Texture/pull/915) ([maicki](https://github.com/maicki)) -- Fix ASDKgram-Swift to avoid 'error parsing JSON within PhotoModel Init' [\#913](https://github.com/TextureGroup/Texture/pull/913) ([kenstir](https://github.com/kenstir)) -- \#trivial Add forgotten experiment into Schemas/configuration.json [\#912](https://github.com/TextureGroup/Texture/pull/912) ([garrettmoon](https://github.com/garrettmoon)) -- \#trivial Fix the C++ assertion [\#911](https://github.com/TextureGroup/Texture/pull/911) ([garrettmoon](https://github.com/garrettmoon)) -- Add 'iDiva - Beauty & Wedding tips' to Showcase [\#909](https://github.com/TextureGroup/Texture/pull/909) ([sudhanshutil](https://github.com/sudhanshutil)) -- Issue ASNetworkImageNode callbacks off main thread [\#908](https://github.com/TextureGroup/Texture/pull/908) ([garrettmoon](https://github.com/garrettmoon)) -- \[ASTextNode\] Fix a deadlock that could occur when enabling experimental ASTextNode2 via ASConfiguration [\#903](https://github.com/TextureGroup/Texture/pull/903) ([appleguy](https://github.com/appleguy)) -- \[Docs\] Add new lightning talk from Buffer \#trivial [\#902](https://github.com/TextureGroup/Texture/pull/902) ([ay8s](https://github.com/ay8s)) -- Request std=c++11 dialect again, and add warning [\#900](https://github.com/TextureGroup/Texture/pull/900) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[ASTextNode\] Check variables before calling delegate method \#trivial [\#898](https://github.com/TextureGroup/Texture/pull/898) ([Jauzee](https://github.com/Jauzee)) -- ASDKFastImageNamed UIImage initializer nullability \#trivial [\#897](https://github.com/TextureGroup/Texture/pull/897) ([alexhillc](https://github.com/alexhillc)) -- \#trivial Fixes an issue where playback may not start [\#896](https://github.com/TextureGroup/Texture/pull/896) ([garrettmoon](https://github.com/garrettmoon)) -- Update configuration schema \#trivial [\#893](https://github.com/TextureGroup/Texture/pull/893) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- replace ` with code in containers-overview.md [\#884](https://github.com/TextureGroup/Texture/pull/884) ([everettjf](https://github.com/everettjf)) -- \[Docs\] Fix typos in layout specs section \#trivial [\#883](https://github.com/TextureGroup/Texture/pull/883) ([morozkin](https://github.com/morozkin)) -- Match interfacestate update sequence to uikit [\#882](https://github.com/TextureGroup/Texture/pull/882) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- Add experiment to skip creating UIViews altogether for constants [\#881](https://github.com/TextureGroup/Texture/pull/881) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Fix ASDISPLAYNODE\_ASSERTIONS\_ENABLED and ASDefaultPlaybackButton warnings \#trivial [\#880](https://github.com/TextureGroup/Texture/pull/880) ([maicki](https://github.com/maicki)) -- Fix macro definition for AS\_KDEBUG\_ENABLE producing warning \#trivial [\#879](https://github.com/TextureGroup/Texture/pull/879) ([andrewrohn](https://github.com/andrewrohn)) -- Fix pager node for interface coalescing [\#877](https://github.com/TextureGroup/Texture/pull/877) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- Standardize Property Declaration Style in Core Classes [\#870](https://github.com/TextureGroup/Texture/pull/870) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[NoCopyRendering\] In non-VM case, use calloc to get a zerod buffer \#trivial [\#869](https://github.com/TextureGroup/Texture/pull/869) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Check in Xcode 9.3 "workspace checks" file [\#868](https://github.com/TextureGroup/Texture/pull/868) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Remove Redundant Atomic Store from Recursive Unfair Lock in Recursive Case \#trivial [\#867](https://github.com/TextureGroup/Texture/pull/867) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Update Podspec [\#866](https://github.com/TextureGroup/Texture/pull/866) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[Issue 838\] Update ASCeilPixelValue and ASRoundPixelValue [\#864](https://github.com/TextureGroup/Texture/pull/864) ([rcancro](https://github.com/rcancro)) -- Disable interface coalescing [\#862](https://github.com/TextureGroup/Texture/pull/862) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- Introduce ASRecursiveUnfairLock and tests [\#858](https://github.com/TextureGroup/Texture/pull/858) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Make NSIndexSet+ASHelpers.h reference local \#trivial [\#857](https://github.com/TextureGroup/Texture/pull/857) ([dmaclach](https://github.com/dmaclach)) -- Make ASBatchContext lock-free \#trivial [\#854](https://github.com/TextureGroup/Texture/pull/854) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[ASNetworkImageNode\] Replace NSUUID sentinel with integer \#trivial [\#852](https://github.com/TextureGroup/Texture/pull/852) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Make objects conform to NSLocking [\#851](https://github.com/TextureGroup/Texture/pull/851) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Make cache support animated image [\#850](https://github.com/TextureGroup/Texture/pull/850) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- \[bugfix\] Align timing of interface coalescing and range update. \#trivial [\#847](https://github.com/TextureGroup/Texture/pull/847) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- Update layout2-layout-element-properties.md [\#844](https://github.com/TextureGroup/Texture/pull/844) ([arielelkin](https://github.com/arielelkin)) -- Use NS\_RETURNS\_RETAINED macro to save time [\#843](https://github.com/TextureGroup/Texture/pull/843) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Handle nil backgroundColor in ASTextNode2 \#trivial [\#841](https://github.com/TextureGroup/Texture/pull/841) ([maicki](https://github.com/maicki)) -- Put back VM flag in ASCGImageBuffer [\#839](https://github.com/TextureGroup/Texture/pull/839) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Order items in XCode project navigator by name [\#835](https://github.com/TextureGroup/Texture/pull/835) ([OleksiyA](https://github.com/OleksiyA)) -- \[NoCopyRendering\] Use vm instead of malloc [\#833](https://github.com/TextureGroup/Texture/pull/833) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[ASTextNode2\] Fix background color drawing [\#831](https://github.com/TextureGroup/Texture/pull/831) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Fix Text Node Thread Sanitizer Warning [\#830](https://github.com/TextureGroup/Texture/pull/830) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- access view first before checking canBecome/Resign responder in becomeResponder methods [\#829](https://github.com/TextureGroup/Texture/pull/829) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- \[\#trivial\] fixes rendered image quality on networked image nodes whic… [\#826](https://github.com/TextureGroup/Texture/pull/826) ([garrettmoon](https://github.com/garrettmoon)) -- \[ASTextNode\] Fix locking, add test for issue \#trivial [\#825](https://github.com/TextureGroup/Texture/pull/825) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[\#trivial\] I don't think we need this extra locked method. [\#824](https://github.com/TextureGroup/Texture/pull/824) ([garrettmoon](https://github.com/garrettmoon)) -- \#trivial Hopefully made this a bit more readable. [\#823](https://github.com/TextureGroup/Texture/pull/823) ([garrettmoon](https://github.com/garrettmoon)) -- \[ASTextNode\] Avoid acquiring instance lock multiple times \#trivial [\#820](https://github.com/TextureGroup/Texture/pull/820) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[Showcase\] Fix mensXP showcase and attach Vingle-Tech-Talk Medium [\#818](https://github.com/TextureGroup/Texture/pull/818) ([GeekTree0101](https://github.com/GeekTree0101)) -- \[ASDisplayNode\] Add unit tests for layout z-order changes \(with an open issue to fix\). [\#816](https://github.com/TextureGroup/Texture/pull/816) ([appleguy](https://github.com/appleguy)) -- \[ASDKGram Example\] image\_url has been changed from URL string to Array by 5… [\#813](https://github.com/TextureGroup/Texture/pull/813) ([kaar3k](https://github.com/kaar3k)) -- Replace pthread specifics with C11 thread-local variables [\#811](https://github.com/TextureGroup/Texture/pull/811) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Upgrade dangerfile [\#810](https://github.com/TextureGroup/Texture/pull/810) ([garrettmoon](https://github.com/garrettmoon)) -- \[ASDisplayNode\] Fix an issue that causes a node to sometimes return an outdated calculated size or size range [\#808](https://github.com/TextureGroup/Texture/pull/808) ([nguyenhuy](https://github.com/nguyenhuy)) -- Avoid triggering main thread assertions in collection/table dealloc \#trivial [\#803](https://github.com/TextureGroup/Texture/pull/803) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Update IGListKit dependency to allow for updated versions [\#802](https://github.com/TextureGroup/Texture/pull/802) ([johntmcintosh](https://github.com/johntmcintosh)) -- \[ASDisplayNode\] Consolidate main thread initialization and allow apps to invoke it manually instead of +load. [\#798](https://github.com/TextureGroup/Texture/pull/798) ([appleguy](https://github.com/appleguy)) -- \[ASWrapperCellNode\] Introduce a new class allowing more control of UIKit passthrough cells. [\#797](https://github.com/TextureGroup/Texture/pull/797) ([appleguy](https://github.com/appleguy)) -- Add missing scrollViewWillEndDragging passthrough delegate [\#796](https://github.com/TextureGroup/Texture/pull/796) ([xezero](https://github.com/xezero)) -- Fix ASTextNode2 is accessing backgroundColor off main while sizing / layout is happening [\#794](https://github.com/TextureGroup/Texture/pull/794) ([maicki](https://github.com/maicki)) -- \[ASTableNode & ASCollectionNode\] Keepalive reference for node if their view is necessarily alive \(has a superview\). [\#793](https://github.com/TextureGroup/Texture/pull/793) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- \[ASDisplayNode layout\] Fix an issue that sometimes causes a node's pending layout to not be applied [\#792](https://github.com/TextureGroup/Texture/pull/792) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[ASRangeController\] Fix stability of "minimum" rangeMode if the app has more than one layout before scrolling. [\#790](https://github.com/TextureGroup/Texture/pull/790) ([appleguy](https://github.com/appleguy)) -- Fix UIResponder handling with view backing ASDisplayNode [\#789](https://github.com/TextureGroup/Texture/pull/789) ([maicki](https://github.com/maicki)) -- New runloop queue to coalesce Interface state update calls. [\#788](https://github.com/TextureGroup/Texture/pull/788) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- \[Graphics contexts\] Retain the reference color space \#trivial [\#784](https://github.com/TextureGroup/Texture/pull/784) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Get CatDealsCollectionView example running again \#trivial [\#783](https://github.com/TextureGroup/Texture/pull/783) ([maicki](https://github.com/maicki)) -- Improve nullable annotations for \_ASDisplayLayer and \_ASDisplayView \#trivial [\#780](https://github.com/TextureGroup/Texture/pull/780) ([maicki](https://github.com/maicki)) -- \[ASDisplayNode\] Force a layout pass on a visible node as soon as it enters preload state [\#779](https://github.com/TextureGroup/Texture/pull/779) ([nguyenhuy](https://github.com/nguyenhuy)) -- Improve ASNetworkImageNode delegate callout behavior [\#778](https://github.com/TextureGroup/Texture/pull/778) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Fix capturing self in the block while loading image in ASNetworkImageNode [\#777](https://github.com/TextureGroup/Texture/pull/777) ([morozkin](https://github.com/morozkin)) -- Fix synchronous state of node if +viewClass or +layerClass is overwritten \#trivial [\#776](https://github.com/TextureGroup/Texture/pull/776) ([maicki](https://github.com/maicki)) -- Add support for providing additional info to network image node delegate [\#775](https://github.com/TextureGroup/Texture/pull/775) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Expose asyncdisplaykit\_node in \_ASDisplayView same as in \_ASDisplayLayer \#trivial [\#773](https://github.com/TextureGroup/Texture/pull/773) ([maicki](https://github.com/maicki)) -- Improve no-copy rendering experiment, remove +load method [\#771](https://github.com/TextureGroup/Texture/pull/771) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Fix typos in layout2-layoutspec-types.md \#trivial [\#770](https://github.com/TextureGroup/Texture/pull/770) ([morozkin](https://github.com/morozkin)) -- Update PINCache [\#769](https://github.com/TextureGroup/Texture/pull/769) ([justinswart](https://github.com/justinswart)) -- Fix misprint \#trivial [\#768](https://github.com/TextureGroup/Texture/pull/768) ([Flatout73](https://github.com/Flatout73)) -- NoCopyRendering experiment: Fix possible memory leak if image node rendering is canceled \#trivial [\#765](https://github.com/TextureGroup/Texture/pull/765) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Node tint color [\#764](https://github.com/TextureGroup/Texture/pull/764) ([ShogunPhyched](https://github.com/ShogunPhyched)) -- Revert "Faster collection operations" [\#759](https://github.com/TextureGroup/Texture/pull/759) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[ASPrimitiveTraitCollection\] Always treat preferredContentSize as a potential nil \#trivial [\#757](https://github.com/TextureGroup/Texture/pull/757) ([ypogribnyi](https://github.com/ypogribnyi)) -- Update subclassing.md [\#753](https://github.com/TextureGroup/Texture/pull/753) ([janechoi6](https://github.com/janechoi6)) -- \[ASDisplayNode\] Don't force a layout pass on a visible node that enters preload state [\#751](https://github.com/TextureGroup/Texture/pull/751) ([nguyenhuy](https://github.com/nguyenhuy)) -- Fix the dangerfile [\#750](https://github.com/TextureGroup/Texture/pull/750) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[ASDisplayNode\] Always return the thread-safe cornerRadius property, even in slow CALayer rounding mode [\#749](https://github.com/TextureGroup/Texture/pull/749) ([nguyenhuy](https://github.com/nguyenhuy)) -- Faster collection operations [\#748](https://github.com/TextureGroup/Texture/pull/748) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Create a centralized configuration API [\#747](https://github.com/TextureGroup/Texture/pull/747) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Update dangerfile for 2018 \#trivial [\#746](https://github.com/TextureGroup/Texture/pull/746) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Raise deployment target to iOS 9 [\#743](https://github.com/TextureGroup/Texture/pull/743) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Add an experimental "no-copy" renderer [\#741](https://github.com/TextureGroup/Texture/pull/741) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Fixed: completeBatchFetching is called on a background thread [\#731](https://github.com/TextureGroup/Texture/pull/731) ([aaronr93](https://github.com/aaronr93)) -- \[tvOS\] Fixes errors when building against tvOS SDK [\#728](https://github.com/TextureGroup/Texture/pull/728) ([alexhillc](https://github.com/alexhillc)) -- \[ASCellNode\] focusStyle mapping [\#727](https://github.com/TextureGroup/Texture/pull/727) ([alexhillc](https://github.com/alexhillc)) -- \[ASDisplayNode\] Provide safeAreaInsets and layoutMargins bridge [\#685](https://github.com/TextureGroup/Texture/pull/685) ([ypogribnyi](https://github.com/ypogribnyi)) -- \[ASTraitCollection\] Add missing properties to ASTraitCollection [\#625](https://github.com/TextureGroup/Texture/pull/625) ([ypogribnyi](https://github.com/ypogribnyi)) - -## [2.6](https://github.com/TextureGroup/Texture/tree/2.6) (2018-01-12) -[Full Changelog](https://github.com/TextureGroup/Texture/compare/2.5.1...2.6) - -**Merged pull requests:** - -- Add MensXP to Showcase [\#739](https://github.com/TextureGroup/Texture/pull/739) ([sudhanshutil](https://github.com/sudhanshutil)) -- Enable collection node interactive moves [\#735](https://github.com/TextureGroup/Texture/pull/735) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Add Blendle to our showcase page [\#721](https://github.com/TextureGroup/Texture/pull/721) ([nguyenhuy](https://github.com/nguyenhuy)) -- \#trivial Fixes image nodes being stuck not being able to download image [\#720](https://github.com/TextureGroup/Texture/pull/720) ([garrettmoon](https://github.com/garrettmoon)) -- Reimplement ASRectTable using unordered\_map to avoid obscure NSMapTable exception. [\#719](https://github.com/TextureGroup/Texture/pull/719) ([appleguy](https://github.com/appleguy)) -- Add missing flags for ASCollectionDelegate [\#718](https://github.com/TextureGroup/Texture/pull/718) ([ilyailya](https://github.com/ilyailya)) -- Add support for toggling logs off and back on at runtime [\#714](https://github.com/TextureGroup/Texture/pull/714) ([johntmcintosh](https://github.com/johntmcintosh)) -- \[Update Showcase\] Update Showcase, add Vingle very community [\#711](https://github.com/TextureGroup/Texture/pull/711) ([GeekTree0101](https://github.com/GeekTree0101)) -- \[ASCollectionElement\] Check for nil elements on ASTableView as well. [\#710](https://github.com/TextureGroup/Texture/pull/710) ([cesteban](https://github.com/cesteban)) -- Ensure an ASM enabled node applies its pending layout when enters preload state [\#706](https://github.com/TextureGroup/Texture/pull/706) ([nguyenhuy](https://github.com/nguyenhuy)) -- The ASDKgram example doesn't compile. [\#700](https://github.com/TextureGroup/Texture/pull/700) ([onato](https://github.com/onato)) -- Revert Adds support for specifying a quality indexed array of URLs [\#699](https://github.com/TextureGroup/Texture/pull/699) ([garrettmoon](https://github.com/garrettmoon)) -- Correct Synchronous Concurrency Talk Link [\#698](https://github.com/TextureGroup/Texture/pull/698) ([ay8s](https://github.com/ay8s)) -- \[ASDisplayNode+Layout\] Ensure a pending layout is applied once [\#695](https://github.com/TextureGroup/Texture/pull/695) ([nguyenhuy](https://github.com/nguyenhuy)) -- Add missing \ tags in Layout API Sizing docs [\#691](https://github.com/TextureGroup/Texture/pull/691) ([richardhenry](https://github.com/richardhenry)) -- Fix bug that breaks ASNodeController docs page [\#690](https://github.com/TextureGroup/Texture/pull/690) ([richardhenry](https://github.com/richardhenry)) -- Add a recent talk by @smeis at CocoaHeadsNL [\#687](https://github.com/TextureGroup/Texture/pull/687) ([nguyenhuy](https://github.com/nguyenhuy)) -- Update subtree-rasterization.md [\#679](https://github.com/TextureGroup/Texture/pull/679) ([WymzeeLabs](https://github.com/WymzeeLabs)) -- Update layer-backing.md [\#678](https://github.com/TextureGroup/Texture/pull/678) ([WymzeeLabs](https://github.com/WymzeeLabs)) -- \[iOS11\] Update project settings and fix errors [\#676](https://github.com/TextureGroup/Texture/pull/676) ([Eke](https://github.com/Eke)) -- Fix swift sample. [\#669](https://github.com/TextureGroup/Texture/pull/669) ([rwinzhang](https://github.com/rwinzhang)) -- Bugfix/fix yoga logging aligning api changes [\#668](https://github.com/TextureGroup/Texture/pull/668) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- Make it possible to map between sections even if they're empty [\#660](https://github.com/TextureGroup/Texture/pull/660) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[ASCornerLayoutSpec\] New layout spec class for declarative corner element layout. [\#657](https://github.com/TextureGroup/Texture/pull/657) ([huang-kun](https://github.com/huang-kun)) -- Update layout2-layoutspec-types.md [\#655](https://github.com/TextureGroup/Texture/pull/655) ([TBXark](https://github.com/TBXark)) -- \[Minor Breaking API\] Make deallocation queues more reliable [\#651](https://github.com/TextureGroup/Texture/pull/651) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Make the framework backwards compatible with Xcode 8 [\#650](https://github.com/TextureGroup/Texture/pull/650) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Disable this test for now, it's too flakey and no one has time to inv… [\#649](https://github.com/TextureGroup/Texture/pull/649) ([garrettmoon](https://github.com/garrettmoon)) -- \[Documentation\] Update Inversion Docs [\#647](https://github.com/TextureGroup/Texture/pull/647) ([GeekTree0101](https://github.com/GeekTree0101)) -- Have ASNetworkImageNode report whether images were cached or not [\#639](https://github.com/TextureGroup/Texture/pull/639) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Fix a layout deadlock caused by holding the lock and going up the tree. [\#638](https://github.com/TextureGroup/Texture/pull/638) ([garrettmoon](https://github.com/garrettmoon)) -- \[ASScrollNode\] Fix small bugs and add unit tests [\#637](https://github.com/TextureGroup/Texture/pull/637) ([nguyenhuy](https://github.com/nguyenhuy)) -- A couple performance tweaks for animated images \#trivial [\#634](https://github.com/TextureGroup/Texture/pull/634) ([garrettmoon](https://github.com/garrettmoon)) -- \[Documentation\] Update "Getting Started" page [\#633](https://github.com/TextureGroup/Texture/pull/633) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[Tests\] Add test scrollToPageAtIndex ASPagerNode [\#629](https://github.com/TextureGroup/Texture/pull/629) ([remirobert](https://github.com/remirobert)) -- \[Tests\] Introducing tests for the ASTabBarController [\#628](https://github.com/TextureGroup/Texture/pull/628) ([remirobert](https://github.com/remirobert)) -- \[Tests\] Introducing tests for the ASNavigationController [\#627](https://github.com/TextureGroup/Texture/pull/627) ([remirobert](https://github.com/remirobert)) -- \[ASCollectionView\] Call -invalidateFlowLayoutDelegateMetrics when rotating. \#trivial [\#616](https://github.com/TextureGroup/Texture/pull/616) ([appleguy](https://github.com/appleguy)) -- Add unit tests for the layout engine [\#424](https://github.com/TextureGroup/Texture/pull/424) ([Adlai-Holler](https://github.com/Adlai-Holler)) - -## [2.5.1](https://github.com/TextureGroup/Texture/tree/2.5.1) (2017-10-24) -[Full Changelog](https://github.com/TextureGroup/Texture/compare/2.5...2.5.1) - -**Merged pull requests:** - -- Dispatch batch update to main \#trivial [\#626](https://github.com/TextureGroup/Texture/pull/626) ([garrettmoon](https://github.com/garrettmoon)) -- Check if we need to do a batch update [\#624](https://github.com/TextureGroup/Texture/pull/624) ([garrettmoon](https://github.com/garrettmoon)) -- Fix naming conflict with YYText \#trivial [\#623](https://github.com/TextureGroup/Texture/pull/623) ([maicki](https://github.com/maicki)) -- Fix "This block and function declaration is not a prototype" warning. [\#619](https://github.com/TextureGroup/Texture/pull/619) ([mbesnili](https://github.com/mbesnili)) -- update Pinterest CDN URL in example code [\#613](https://github.com/TextureGroup/Texture/pull/613) ([derekargueta](https://github.com/derekargueta)) -- \[ASTextKitComponents\] Make sure Main Thread Checker isn't triggered during background calculations \#trivial [\#612](https://github.com/TextureGroup/Texture/pull/612) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[ASTextKitComponents\] Temporary components can be deallocated off main \#trivial [\#610](https://github.com/TextureGroup/Texture/pull/610) ([nguyenhuy](https://github.com/nguyenhuy)) -- Update layout2-layoutspec-types.md [\#608](https://github.com/TextureGroup/Texture/pull/608) ([olcayertas](https://github.com/olcayertas)) -- Don't set download results if no longer in preload range. [\#606](https://github.com/TextureGroup/Texture/pull/606) ([garrettmoon](https://github.com/garrettmoon)) -- Animated WebP support [\#605](https://github.com/TextureGroup/Texture/pull/605) ([garrettmoon](https://github.com/garrettmoon)) -- \[ASVideoNode\] Time observer fix [\#604](https://github.com/TextureGroup/Texture/pull/604) ([flovouin](https://github.com/flovouin)) -- Add assertion in dealloc that it is on main in ASTextKitComponents \#trivial [\#603](https://github.com/TextureGroup/Texture/pull/603) ([maicki](https://github.com/maicki)) -- ASTextKitComponents needs to be deallocated on main [\#598](https://github.com/TextureGroup/Texture/pull/598) ([maicki](https://github.com/maicki)) -- update faq toc links to match the generated html id \#trivial [\#597](https://github.com/TextureGroup/Texture/pull/597) ([romankl](https://github.com/romankl)) -- \[PINCache\] Set a default .byteLimit to reduce disk usage & startup time. [\#595](https://github.com/TextureGroup/Texture/pull/595) ([appleguy](https://github.com/appleguy)) -- Move clearing out of ASTextKitComponents property delegates into ASTextKitComponents dealloc \#trivial [\#591](https://github.com/TextureGroup/Texture/pull/591) ([maicki](https://github.com/maicki)) -- Clear ivar after scheduling for main thread deallocation \#trivial [\#590](https://github.com/TextureGroup/Texture/pull/590) ([maicki](https://github.com/maicki)) -- Use Nil for "no class" instead of nil \#trivial [\#589](https://github.com/TextureGroup/Texture/pull/589) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Update showcase.md [\#587](https://github.com/TextureGroup/Texture/pull/587) ([hannahmbanana](https://github.com/hannahmbanana)) -- Rolling back CI to known version for now [\#585](https://github.com/TextureGroup/Texture/pull/585) ([garrettmoon](https://github.com/garrettmoon)) -- Use node lock instead of separate one to avoid deadlocks. [\#582](https://github.com/TextureGroup/Texture/pull/582) ([garrettmoon](https://github.com/garrettmoon)) -- \[\_ASPendingState\] Make sure accessibility strings are not nil before allocating attributed strings for them \#trivial [\#581](https://github.com/TextureGroup/Texture/pull/581) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[ASTextNode\] Implement an example comparing ASTextNode 1 & 2 behavior. [\#570](https://github.com/TextureGroup/Texture/pull/570) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- \[ASCollectionView\] Fix index space translation of Flow Layout Delegate methods. [\#467](https://github.com/TextureGroup/Texture/pull/467) ([appleguy](https://github.com/appleguy)) -- \[ASCollectionView\] Improve performance and behavior of rotation / bounds changes. [\#431](https://github.com/TextureGroup/Texture/pull/431) ([appleguy](https://github.com/appleguy)) - -## [2.5](https://github.com/TextureGroup/Texture/tree/2.5) (2017-09-26) -[Full Changelog](https://github.com/TextureGroup/Texture/compare/v2.5...2.5) - -**Merged pull requests:** - -- Fix crashes caused by failing to unlock or destroy a static mutex while the app is being terminated [\#577](https://github.com/TextureGroup/Texture/pull/577) ([nguyenhuy](https://github.com/nguyenhuy)) -- Update yoga version [\#569](https://github.com/TextureGroup/Texture/pull/569) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- \[ASDKgram Example\] fix crash on startup [\#566](https://github.com/TextureGroup/Texture/pull/566) ([hannahmbanana](https://github.com/hannahmbanana)) -- Added attributed versions of accessibilityLabel, accessibilityHint, accessibilityValue [\#554](https://github.com/TextureGroup/Texture/pull/554) ([fruitcoder](https://github.com/fruitcoder)) -- \[Yoga\] Add insertYogaNode:atIndex: method. Improve handling of relayouts. [\#469](https://github.com/TextureGroup/Texture/pull/469) ([appleguy](https://github.com/appleguy)) -- \[ASCornerRounding\] Introduce .cornerRoundingType: CALayer, Precomposited, or Clip Corners. [\#465](https://github.com/TextureGroup/Texture/pull/465) ([appleguy](https://github.com/appleguy)) -- \[ASElementMap\] Fix indexPath's section or item is actually negative \#trivial [\#457](https://github.com/TextureGroup/Texture/pull/457) ([Anyewuya](https://github.com/Anyewuya)) - -## [v2.5](https://github.com/TextureGroup/Texture/tree/v2.5) (2017-09-14) -[Full Changelog](https://github.com/TextureGroup/Texture/compare/2.4...v2.5) - -**Merged pull requests:** - -- Fix -\[ASPagerNode view\] triggering pendingState + nodeLoaded assert \#trivial [\#564](https://github.com/TextureGroup/Texture/pull/564) ([samhsiung](https://github.com/samhsiung)) -- \[ASCollectionLayout\] Exclude content inset on scrollable directions from viewport size [\#562](https://github.com/TextureGroup/Texture/pull/562) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[ASImageNode\] Always dealloc images in a background queue [\#561](https://github.com/TextureGroup/Texture/pull/561) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[ASCollectionNode\]\[ASTableNode\] Add content inset bridging property [\#560](https://github.com/TextureGroup/Texture/pull/560) ([nguyenhuy](https://github.com/nguyenhuy)) -- Mark ASRunLoopQueue as drained if it contains only NULLs [\#558](https://github.com/TextureGroup/Texture/pull/558) ([cesteban](https://github.com/cesteban)) -- Adds support for specifying a quality indexed array of URLs [\#557](https://github.com/TextureGroup/Texture/pull/557) ([garrettmoon](https://github.com/garrettmoon)) -- Make ASWeakMapEntry Value Atomic [\#555](https://github.com/TextureGroup/Texture/pull/555) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[ASDisplayNode\] Deprecate -displayWillStart in favor of -displayWillStartAsynchronously: [\#536](https://github.com/TextureGroup/Texture/pull/536) ([nguyenhuy](https://github.com/nguyenhuy)) -- SEP-491 prerequisite: add textViewShouldBeginEditing: to ASEditableTextNodeDelegate [\#535](https://github.com/TextureGroup/Texture/pull/535) ([yans](https://github.com/yans)) -- \[Gallery layout\] Include the caller in properties providing methods [\#533](https://github.com/TextureGroup/Texture/pull/533) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[ASDisplayNode\] Notify rasterized subnodes that render pass has completed [\#532](https://github.com/TextureGroup/Texture/pull/532) ([smeis](https://github.com/smeis)) -- \[Cleanup\] Remove deprecated APIs [\#529](https://github.com/TextureGroup/Texture/pull/529) ([nguyenhuy](https://github.com/nguyenhuy)) -- Add a function to disable all logging at runtime [\#528](https://github.com/TextureGroup/Texture/pull/528) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[Table and collection views\] Consider content inset when calculating \(default\) element size range [\#525](https://github.com/TextureGroup/Texture/pull/525) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[ASCollectionNode\] Add -isProcessingUpdates and -onDidFinishProcessingUpdates: APIs. [\#522](https://github.com/TextureGroup/Texture/pull/522) ([appleguy](https://github.com/appleguy)) -- ASImageNode+AnimatedImage playbackReadyCallback retain cycle [\#520](https://github.com/TextureGroup/Texture/pull/520) ([plarson](https://github.com/plarson)) -- \[CI\] BuildKite to ignore all markdown files [\#517](https://github.com/TextureGroup/Texture/pull/517) ([nguyenhuy](https://github.com/nguyenhuy)) -- ASCollectionLayout improvements [\#513](https://github.com/TextureGroup/Texture/pull/513) ([nguyenhuy](https://github.com/nguyenhuy)) -- Update changelog and podspec for 2.4 [\#512](https://github.com/TextureGroup/Texture/pull/512) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- ASCollectionLayout to return a zero content size if its state is unavailable [\#509](https://github.com/TextureGroup/Texture/pull/509) ([nguyenhuy](https://github.com/nguyenhuy)) -- Update corner-rounding.md [\#482](https://github.com/TextureGroup/Texture/pull/482) ([oferRounds](https://github.com/oferRounds)) -- \[Accessibility\] Add .isAccessibilityContainer property, allowing automatic aggregation of children's a11y labels. [\#468](https://github.com/TextureGroup/Texture/pull/468) ([appleguy](https://github.com/appleguy)) -- \[ASImageNode\] Enable .clipsToBounds by default \(fix .cornerRadius, GIFs overflow\). [\#466](https://github.com/TextureGroup/Texture/pull/466) ([appleguy](https://github.com/appleguy)) - -## [2.4](https://github.com/TextureGroup/Texture/tree/2.4) (2017-08-15) -[Full Changelog](https://github.com/TextureGroup/Texture/compare/2.3.4...2.4) - -**Merged pull requests:** - -- Avoid re-entrant call to self.view when applying initial pending state [\#510](https://github.com/TextureGroup/Texture/pull/510) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[examples/ASCollectionView\] Register supplementary kinds \#trivial [\#508](https://github.com/TextureGroup/Texture/pull/508) ([nguyenhuy](https://github.com/nguyenhuy)) -- Rename the field again to nodeModel [\#504](https://github.com/TextureGroup/Texture/pull/504) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Rename -\[ASCellNode viewModel\] to -\[ASCellNode nodeViewModel\] to avoid collisions [\#499](https://github.com/TextureGroup/Texture/pull/499) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Fixed typo `UIKIt` [\#497](https://github.com/TextureGroup/Texture/pull/497) ([nixzhu](https://github.com/nixzhu)) -- Improvements in ASCollectionGalleryLayoutDelegate [\#496](https://github.com/TextureGroup/Texture/pull/496) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[Showcase\] Update showcase - add blog post link to ClassDojo icon \#trivial [\#493](https://github.com/TextureGroup/Texture/pull/493) ([Kaspik](https://github.com/Kaspik)) -- \[ASCoreAnimationExtras\] Update documentation for resizbale images \#trivial [\#492](https://github.com/TextureGroup/Texture/pull/492) ([Kaspik](https://github.com/Kaspik)) -- \[ASStackLayoutSpec\] Fix interitem spacing not being reset on new lines and add snapshot tests \#trivial [\#491](https://github.com/TextureGroup/Texture/pull/491) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[Layout Transition\] Avoid calling didComplete method if pending layout transition is nil [\#490](https://github.com/TextureGroup/Texture/pull/490) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[LayoutTransition\] Call \_locked\_constrainedSizeForLayoutPass with the lock actually held \#trivial [\#488](https://github.com/TextureGroup/Texture/pull/488) ([nguyenhuy](https://github.com/nguyenhuy)) -- iOS 11 UITableView automatic height estimation fix [\#485](https://github.com/TextureGroup/Texture/pull/485) ([christianselig](https://github.com/christianselig)) -- Update scroll-node.md [\#484](https://github.com/TextureGroup/Texture/pull/484) ([oferRounds](https://github.com/oferRounds)) -- Update adoption-guide-2-0-beta1.md [\#483](https://github.com/TextureGroup/Texture/pull/483) ([oferRounds](https://github.com/oferRounds)) -- Update subclassing.md [\#479](https://github.com/TextureGroup/Texture/pull/479) ([oferRounds](https://github.com/oferRounds)) -- Invalidate layouts more aggressively when transitioning with animation [\#476](https://github.com/TextureGroup/Texture/pull/476) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Update image-modification-block.md [\#474](https://github.com/TextureGroup/Texture/pull/474) ([oferRounds](https://github.com/oferRounds)) -- \[ASStackLayoutSpec\] Flex wrap fix and lineSpacing property [\#472](https://github.com/TextureGroup/Texture/pull/472) ([flovouin](https://github.com/flovouin)) -- \[ASNodeController\] Add -nodeDidLayout callback. Allow switching retain behavior at runtime. [\#470](https://github.com/TextureGroup/Texture/pull/470) ([appleguy](https://github.com/appleguy)) -- \[Layout transition\] Invalidate calculated layout if transitioning using the same size range [\#464](https://github.com/TextureGroup/Texture/pull/464) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[ASTableNode\]\[ASCollectionNode\] Add content offset bridging property [\#460](https://github.com/TextureGroup/Texture/pull/460) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[ASDisplayNode\] Fix infinite layout loop [\#455](https://github.com/TextureGroup/Texture/pull/455) ([nguyenhuy](https://github.com/nguyenhuy)) -- Add ASPagerNode+Beta to umbrella header \#trivial [\#454](https://github.com/TextureGroup/Texture/pull/454) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[ASPagerNode\] Remove unused flow layout reference \#trivial [\#452](https://github.com/TextureGroup/Texture/pull/452) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[ASCollectionLayout\] Add ASCollectionGalleryLayoutSizeProviding [\#451](https://github.com/TextureGroup/Texture/pull/451) ([nguyenhuy](https://github.com/nguyenhuy)) -- fix SIMULATE\_WEB\_RESPONSE not imported \#449 [\#450](https://github.com/TextureGroup/Texture/pull/450) ([wsdwsd0829](https://github.com/wsdwsd0829)) -- \[ASDataController \] Merge willUpdateWithChangeSet and didUpdateWithChangeSet delegate methods \#trivial [\#445](https://github.com/TextureGroup/Texture/pull/445) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[ASDataController\] Clean up [\#443](https://github.com/TextureGroup/Texture/pull/443) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[ASDataController\] Avoid asking for size ranges of soon-to-be-delete elements during relayouts [\#442](https://github.com/TextureGroup/Texture/pull/442) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[ASCollectionView\] Add delegate bridging and index space translation for missing UICollectionViewLayout properties. [\#440](https://github.com/TextureGroup/Texture/pull/440) ([appleguy](https://github.com/appleguy)) -- \[ASDisplayNode\] Fix some gaps in the bridging of new contents\* properties. [\#435](https://github.com/TextureGroup/Texture/pull/435) ([appleguy](https://github.com/appleguy)) -- \[ASDisplayNode\] Allow setting stretchable contents on nodes; add bridged properties. \#trivial [\#429](https://github.com/TextureGroup/Texture/pull/429) ([appleguy](https://github.com/appleguy)) -- Use a sentinel NSUInteger for node layout data \#trivial [\#428](https://github.com/TextureGroup/Texture/pull/428) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Workaround clang4.0 \ initialization \#trivial [\#426](https://github.com/TextureGroup/Texture/pull/426) ([bkase](https://github.com/bkase)) -- Add missing import in ASDisplayNode+AsyncDisplay \#trivial [\#423](https://github.com/TextureGroup/Texture/pull/423) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[ASCollectionElement\] Add checks for nil element, prior to other PRs landing. [\#421](https://github.com/TextureGroup/Texture/pull/421) ([appleguy](https://github.com/appleguy)) -- \[ASDataController\] Apply new visible map inside batch updates block [\#420](https://github.com/TextureGroup/Texture/pull/420) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[ASVideoPlayerNode\] Check that the video player's delegate implements the didTapFullScreenButtonNode method before calling it \#trivial [\#418](https://github.com/TextureGroup/Texture/pull/418) ([tnev](https://github.com/tnev)) -- \[ASDataController\] Fix a crash in table view caused by executing an empty change set during layoutSubviews [\#416](https://github.com/TextureGroup/Texture/pull/416) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[ASDisplayNode+Layout\] In layoutThatFits:, check and use \_pending layout if valid. [\#413](https://github.com/TextureGroup/Texture/pull/413) ([appleguy](https://github.com/appleguy)) -- Integrate Weaver into ASDKGram [\#412](https://github.com/TextureGroup/Texture/pull/412) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[ASDisplayNode\] -didEnterPreloadState does not need to call -layoutIfNeeded \#trivial [\#411](https://github.com/TextureGroup/Texture/pull/411) ([appleguy](https://github.com/appleguy)) -- \[ASTextNode2\] Provide compiler flag to enable ASTextNode2 for all usages. [\#410](https://github.com/TextureGroup/Texture/pull/410) ([appleguy](https://github.com/appleguy)) -- \[Yoga\] Refine the handling of measurement functions when Yoga is used. [\#408](https://github.com/TextureGroup/Texture/pull/408) ([appleguy](https://github.com/appleguy)) -- \[ASCollectionView\] Small improvements [\#407](https://github.com/TextureGroup/Texture/pull/407) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[Documentation\] Improve description of synchronous concurrency with screenshot and video link. [\#406](https://github.com/TextureGroup/Texture/pull/406) ([appleguy](https://github.com/appleguy)) -- Introduce ASIntegerMap, improve our changeset handling \#trivial [\#405](https://github.com/TextureGroup/Texture/pull/405) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Fix issue where supplementary elements don't track section changes [\#404](https://github.com/TextureGroup/Texture/pull/404) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Overhaul our logging, add activity tracing support. [\#399](https://github.com/TextureGroup/Texture/pull/399) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[ASTextNode2\] Add initial implementation for link handling. [\#396](https://github.com/TextureGroup/Texture/pull/396) ([appleguy](https://github.com/appleguy)) -- Introduce ASCollectionGalleryLayoutDelegate [\#76](https://github.com/TextureGroup/Texture/pull/76) ([nguyenhuy](https://github.com/nguyenhuy)) - -## [2.3.4](https://github.com/TextureGroup/Texture/tree/2.3.4) (2017-06-30) -[Full Changelog](https://github.com/TextureGroup/Texture/compare/2.3.3...2.3.4) - -**Merged pull requests:** - -- Update to the latest betas of PINRemoteImage and PINCache [\#403](https://github.com/TextureGroup/Texture/pull/403) ([garrettmoon](https://github.com/garrettmoon)) -- A bit of minor cleanup \#trivial [\#402](https://github.com/TextureGroup/Texture/pull/402) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[ASLayout\] Revisit the flattening algorithm [\#395](https://github.com/TextureGroup/Texture/pull/395) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[ASLayout\] If a layout has no sublayouts, don't bother initializing its rect table [\#394](https://github.com/TextureGroup/Texture/pull/394) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[ASLayout\] Fix documentation of retainSublayoutLayoutElements \#trivial [\#393](https://github.com/TextureGroup/Texture/pull/393) ([nguyenhuy](https://github.com/nguyenhuy)) -- Fix compiling ASDimension if Yoga enabled \#trivial [\#389](https://github.com/TextureGroup/Texture/pull/389) ([maicki](https://github.com/maicki)) -- comments to reflect code \#trivial [\#388](https://github.com/TextureGroup/Texture/pull/388) ([benjamin-chang](https://github.com/benjamin-chang)) -- \[ASCellNode\] Remove unnecessary frame setting \#trivial [\#387](https://github.com/TextureGroup/Texture/pull/387) ([nguyenhuy](https://github.com/nguyenhuy)) -- Horrible spelling mistake \#trivial [\#384](https://github.com/TextureGroup/Texture/pull/384) ([nguyenhuy](https://github.com/nguyenhuy)) -- Fix for Video Table Example Building [\#383](https://github.com/TextureGroup/Texture/pull/383) ([ay8s](https://github.com/ay8s)) -- ASDimensionMake to be more lenient \#trivial [\#382](https://github.com/TextureGroup/Texture/pull/382) ([nguyenhuy](https://github.com/nguyenhuy)) -- Gate orphaned node detector behind YOGA flag \#trivial [\#380](https://github.com/TextureGroup/Texture/pull/380) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[Event Log\] Log ASM flag when modify subnodes \#trivial [\#379](https://github.com/TextureGroup/Texture/pull/379) ([nguyenhuy](https://github.com/nguyenhuy)) -- Add new workspaces for tests for different integrations \#trivial [\#377](https://github.com/TextureGroup/Texture/pull/377) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Fix imageModificationBlock doc \#trivial [\#376](https://github.com/TextureGroup/Texture/pull/376) ([maicki](https://github.com/maicki)) -- Fix double-load issue with ASCollectionNode [\#372](https://github.com/TextureGroup/Texture/pull/372) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- FIXED Typo in Layout Transition Documentation [\#371](https://github.com/TextureGroup/Texture/pull/371) ([martinjkelly](https://github.com/martinjkelly)) -- \[Yoga\] Delete YOGA\_TREE\_CONTIGOUS gating and permanently enable. \#trivial [\#370](https://github.com/TextureGroup/Texture/pull/370) ([appleguy](https://github.com/appleguy)) -- \[Yoga\] Minimize number of nodes that have MeasureFunc set on them. [\#369](https://github.com/TextureGroup/Texture/pull/369) ([appleguy](https://github.com/appleguy)) -- Improve System Trace Implementation \#trivial [\#368](https://github.com/TextureGroup/Texture/pull/368) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Updates ASDKGram to use IGListKit 3.0.0 [\#367](https://github.com/TextureGroup/Texture/pull/367) ([ay8s](https://github.com/ay8s)) -- Update link to AsyncDisplayKit 2.0 Launch Talk [\#363](https://github.com/TextureGroup/Texture/pull/363) ([appleguy](https://github.com/appleguy)) -- \[ASTableView\] Use ASTableView tableNode property instead of calling ASViewToDisplayNode \#trivial [\#361](https://github.com/TextureGroup/Texture/pull/361) ([maicki](https://github.com/maicki)) -- Add section-object support to new tests, improve test confinement. \#trivial [\#360](https://github.com/TextureGroup/Texture/pull/360) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[Docs\] Update 'Corner Rounding' document for Texture 2 [\#359](https://github.com/TextureGroup/Texture/pull/359) ([ArchimboldiMao](https://github.com/ArchimboldiMao)) -- Add support for keeping letting cell nodes update to new view models when reloaded. \#trivial [\#357](https://github.com/TextureGroup/Texture/pull/357) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Add first-pass view model support to collection node. \#trivial [\#356](https://github.com/TextureGroup/Texture/pull/356) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[ASTraitCollection\] Convert ASPrimitiveTraitCollection from lock to atomic. [\#355](https://github.com/TextureGroup/Texture/pull/355) ([appleguy](https://github.com/appleguy)) -- Improve collection node testing, reveal double-load issue. \#trivial [\#352](https://github.com/TextureGroup/Texture/pull/352) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Fix title in changelog [\#350](https://github.com/TextureGroup/Texture/pull/350) ([levi](https://github.com/levi)) -- Add a Flag to Disable Main Thread Assertions \#trivial [\#348](https://github.com/TextureGroup/Texture/pull/348) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Migrate to Latest OCMock, Demonstrate Improved Unit Testing [\#347](https://github.com/TextureGroup/Texture/pull/347) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Upgrade ASLayoutElementContext to an Object \#trivial [\#344](https://github.com/TextureGroup/Texture/pull/344) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[Yoga\] Rewrite YOGA\_TREE\_CONTIGUOUS mode with improved behavior and cleaner integration [\#343](https://github.com/TextureGroup/Texture/pull/343) ([appleguy](https://github.com/appleguy)) -- Fix internal Linter warnings \#trivial [\#340](https://github.com/TextureGroup/Texture/pull/340) ([maicki](https://github.com/maicki)) -- Small changes required by the coming layout debugger [\#337](https://github.com/TextureGroup/Texture/pull/337) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[ASDataController\] Add event logging for transaction queue flush duration \#trivial [\#334](https://github.com/TextureGroup/Texture/pull/334) ([hannahmbanana](https://github.com/hannahmbanana)) -- \[ASCollectionView\] synchronous mode [\#332](https://github.com/TextureGroup/Texture/pull/332) ([hannahmbanana](https://github.com/hannahmbanana)) -- \[Performance\] Convert ASLayoutElementSize to atomic \#trivial [\#331](https://github.com/TextureGroup/Texture/pull/331) ([hannahmbanana](https://github.com/hannahmbanana)) -- \[Yoga\] Refer to proper path name and use module import [\#306](https://github.com/TextureGroup/Texture/pull/306) ([weibel](https://github.com/weibel)) -- \[ASImageNode\] Add documentation for image effects \#trivial [\#263](https://github.com/TextureGroup/Texture/pull/263) ([maicki](https://github.com/maicki)) - -## [2.3.3](https://github.com/TextureGroup/Texture/tree/2.3.3) (2017-06-06) -[Full Changelog](https://github.com/TextureGroup/Texture/compare/2.3.2...2.3.3) - -**Merged pull requests:** - -- Updating to 2.3.3 \#trivial [\#338](https://github.com/TextureGroup/Texture/pull/338) ([garrettmoon](https://github.com/garrettmoon)) -- \[ASDisplayNode+Layout\] Add check for orphaned nodes after layout transition to clean up. [\#336](https://github.com/TextureGroup/Texture/pull/336) ([appleguy](https://github.com/appleguy)) -- Update PINRemoteImage [\#328](https://github.com/TextureGroup/Texture/pull/328) ([garrettmoon](https://github.com/garrettmoon)) -- Fix typo \#trivial [\#327](https://github.com/TextureGroup/Texture/pull/327) ([vitalybaev](https://github.com/vitalybaev)) -- Fixes an issue with GIFs that would always be covered by their placeh… [\#326](https://github.com/TextureGroup/Texture/pull/326) ([garrettmoon](https://github.com/garrettmoon)) -- Replace NSMutableSet with NSHashTable when Appropriate \#trivial [\#321](https://github.com/TextureGroup/Texture/pull/321) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[Cleanup\] Small fixes to improve conformance for strict compiler settings \#trivial [\#320](https://github.com/TextureGroup/Texture/pull/320) ([appleguy](https://github.com/appleguy)) -- Rejigger Cell Visibility Tracking [\#317](https://github.com/TextureGroup/Texture/pull/317) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Clean Up ASAsyncTransaction \#trivial [\#316](https://github.com/TextureGroup/Texture/pull/316) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Clean Up ASDisplayLayer \#trivial [\#315](https://github.com/TextureGroup/Texture/pull/315) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[ASDisplayNode\] Revise assertion to log until Issue \#145 is addressed. \#trivial [\#313](https://github.com/TextureGroup/Texture/pull/313) ([appleguy](https://github.com/appleguy)) -- \[Docs\] Fixed typo in carthage project name \#trivial [\#310](https://github.com/TextureGroup/Texture/pull/310) ([george-gw](https://github.com/george-gw)) -- Fix non layout [\#309](https://github.com/TextureGroup/Texture/pull/309) ([garrettmoon](https://github.com/garrettmoon)) -- Catch Invalid Layer Bounds in a Nonfatal Assertion \#trivial [\#308](https://github.com/TextureGroup/Texture/pull/308) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[ASCollectionNode\] Fix missing properties and layoutInspector \#trivial [\#305](https://github.com/TextureGroup/Texture/pull/305) ([flovouin](https://github.com/flovouin)) -- \[Examples\] Fixed crash on SocialAppLayout-Inverted + behaviour comments [\#304](https://github.com/TextureGroup/Texture/pull/304) ([dimazen](https://github.com/dimazen)) -- IGListKit related headers need to be in the module all time now \#trivial [\#300](https://github.com/TextureGroup/Texture/pull/300) ([maicki](https://github.com/maicki)) -- \[ASDisplayNode\] Remove assertion in calculateSizeThatFits: and log an event \#trivial [\#299](https://github.com/TextureGroup/Texture/pull/299) ([maicki](https://github.com/maicki)) -- ASBatchFetching to not round scroll velocity \#trivial [\#294](https://github.com/TextureGroup/Texture/pull/294) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[ASVideoNodeDelegate\] fix for \#291 crash [\#292](https://github.com/TextureGroup/Texture/pull/292) ([SergeyPetrachkov](https://github.com/SergeyPetrachkov)) -- Fix Alignment of Hashed Structs [\#287](https://github.com/TextureGroup/Texture/pull/287) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[IGListKit\] Add IGListKit headers to public section of Xcode project [\#286](https://github.com/TextureGroup/Texture/pull/286) ([maicki](https://github.com/maicki)) -- Only call -layout and -layoutDidFinish if the node is already loaded [\#285](https://github.com/TextureGroup/Texture/pull/285) ([nguyenhuy](https://github.com/nguyenhuy)) -- \[Batch Fetching\] Add ASBatchFetchingDelegate [\#281](https://github.com/TextureGroup/Texture/pull/281) ([nguyenhuy](https://github.com/nguyenhuy)) -- Ignore Relayout Requests for Deleted Cell Nodes [\#279](https://github.com/TextureGroup/Texture/pull/279) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Remove Unused Node Code \#trivial [\#278](https://github.com/TextureGroup/Texture/pull/278) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Fix Documentation Warnings \#trivial [\#276](https://github.com/TextureGroup/Texture/pull/276) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[Examples\] Fix LayoutSpecExamples and LayoutSpecExamples-Swift: image URLs were still pointing to asyncdisplaykit.org [\#275](https://github.com/TextureGroup/Texture/pull/275) ([cesteban](https://github.com/cesteban)) -- \[Layout\] Extract layout implementation code into it's own subcategories [\#272](https://github.com/TextureGroup/Texture/pull/272) ([maicki](https://github.com/maicki)) -- Fix Release Builds \#trivial [\#271](https://github.com/TextureGroup/Texture/pull/271) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[Yoga\] Implement ASYogaLayoutSpec, a simplified integration strategy for Yoga. [\#270](https://github.com/TextureGroup/Texture/pull/270) ([appleguy](https://github.com/appleguy)) -- Simplify Layout Transition State \#trivial [\#269](https://github.com/TextureGroup/Texture/pull/269) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Store ASLayoutElementContext in Thread-Local Storage \#trivial [\#268](https://github.com/TextureGroup/Texture/pull/268) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[Examples\] Fix a couple of examples due to API changes recently \#trivial [\#267](https://github.com/TextureGroup/Texture/pull/267) ([maicki](https://github.com/maicki)) -- Fix Collection Item Index Path Conversion [\#262](https://github.com/TextureGroup/Texture/pull/262) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- added error reporting callback to ASVideoNode [\#260](https://github.com/TextureGroup/Texture/pull/260) ([SergeyPetrachkov](https://github.com/SergeyPetrachkov)) -- Add Experimental Text Node Implementation [\#259](https://github.com/TextureGroup/Texture/pull/259) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Add missing import and define in ASLog \#trivial [\#257](https://github.com/TextureGroup/Texture/pull/257) ([nguyenhuy](https://github.com/nguyenhuy)) -- Simplify Override Checking, Only Do It When Assertions Are Enabled \#trivial [\#253](https://github.com/TextureGroup/Texture/pull/253) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[ASTextKitFontSizeAdjuster\] Replace use of boundingRectWithSize:options:context: with boundingRectForGlyphRange: inTextContainer: [\#251](https://github.com/TextureGroup/Texture/pull/251) ([rcancro](https://github.com/rcancro)) -- Improve Ancestry Handling, Avoid Assertion Failure [\#246](https://github.com/TextureGroup/Texture/pull/246) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[Yoga\] Increment Yoga version to current, 1.5.0. [\#91](https://github.com/TextureGroup/Texture/pull/91) ([appleguy](https://github.com/appleguy)) -- \[example/CustomCollectionView\] Implement MosaicCollectionLayoutDelegate [\#28](https://github.com/TextureGroup/Texture/pull/28) ([nguyenhuy](https://github.com/nguyenhuy)) - -## [2.3.2](https://github.com/TextureGroup/Texture/tree/2.3.2) (2017-05-09) -[Full Changelog](https://github.com/TextureGroup/Texture/compare/2.3.1...2.3.2) - -**Merged pull requests:** - -- \[ASDisplayNode\] Pass drawParameter in rendering context callbacks [\#248](https://github.com/TextureGroup/Texture/pull/248) ([maicki](https://github.com/maicki)) -- Assert only once we know URL has changed [\#247](https://github.com/TextureGroup/Texture/pull/247) ([garrettmoon](https://github.com/garrettmoon)) -- \[ASImageNode\] Move to class method of displayWithParameters:isCancelled: for drawing [\#244](https://github.com/TextureGroup/Texture/pull/244) ([maicki](https://github.com/maicki)) -- Don't Use Associated Objects for Drawing Priority \#trivial [\#239](https://github.com/TextureGroup/Texture/pull/239) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[ASDimension\] Remove warning about float precision using CGFloat and … \#trivial [\#237](https://github.com/TextureGroup/Texture/pull/237) ([amegias](https://github.com/amegias)) -- \[ASImageNode\] Move debug label and will- / didDisplayNodeContentWithRenderingContext out of drawing method \#trivial [\#235](https://github.com/TextureGroup/Texture/pull/235) ([maicki](https://github.com/maicki)) -- Fixes assertion on startup in social app layout example [\#233](https://github.com/TextureGroup/Texture/pull/233) ([garrettmoon](https://github.com/garrettmoon)) -- \[ASTextNode\] Move to class method of drawRect:withParameters:isCancelled:isRasterizing: for drawing [\#232](https://github.com/TextureGroup/Texture/pull/232) ([maicki](https://github.com/maicki)) -- \[ASImageNode\] Remove unneeded pointer star \#trivial [\#231](https://github.com/TextureGroup/Texture/pull/231) ([maicki](https://github.com/maicki)) -- \[ASTwoDimensionalArrayUtils\] Fix extern C function definition to fix compiler issue. \#trivial [\#229](https://github.com/TextureGroup/Texture/pull/229) ([appleguy](https://github.com/appleguy)) -- Move Last Few Properties from ASTableView,ASCollectionView to Node [\#225](https://github.com/TextureGroup/Texture/pull/225) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Fix Issues in the Project File \#trivial [\#224](https://github.com/TextureGroup/Texture/pull/224) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Improve Our Handling of Subnodes [\#223](https://github.com/TextureGroup/Texture/pull/223) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Extract ASLayoutElement and ASLayoutElementStylability into categories \#trivial [\#131](https://github.com/TextureGroup/Texture/pull/131) ([maicki](https://github.com/maicki)) -- \[Layout\] Remove finalLayoutElement [\#96](https://github.com/TextureGroup/Texture/pull/96) ([maicki](https://github.com/maicki)) -- \[Docs\] Add workaround for setting a custom lineSpacing and maxNumberOfLines to ASTextNode docs [\#92](https://github.com/TextureGroup/Texture/pull/92) ([maicki](https://github.com/maicki)) -- \[ASDisplayNode\] Implement a std::atomic-based flag system for superb performance [\#89](https://github.com/TextureGroup/Texture/pull/89) ([appleguy](https://github.com/appleguy)) -- \[Yoga\] Ensure that calculated layout is nil'd in invalidate\*Layout [\#87](https://github.com/TextureGroup/Texture/pull/87) ([appleguy](https://github.com/appleguy)) -- Simplify Hashing Code [\#86](https://github.com/TextureGroup/Texture/pull/86) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Fix site header [\#84](https://github.com/TextureGroup/Texture/pull/84) ([levi](https://github.com/levi)) -- Tighten Rasterization API, Undeprecate It [\#82](https://github.com/TextureGroup/Texture/pull/82) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Implement ASPageTable [\#81](https://github.com/TextureGroup/Texture/pull/81) ([nguyenhuy](https://github.com/nguyenhuy)) -- Make Cell Node Properties Atomic [\#74](https://github.com/TextureGroup/Texture/pull/74) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- \[ASNodeController+Beta\] Provide an option to allow nodes to own their controllers. [\#61](https://github.com/TextureGroup/Texture/pull/61) ([appleguy](https://github.com/appleguy)) -- \[Yoga Beta\] Improvements to the experimental support for Yoga layout. [\#59](https://github.com/TextureGroup/Texture/pull/59) ([appleguy](https://github.com/appleguy)) -- Fix issue with swipe to delete cell gesture. \#trivial [\#46](https://github.com/TextureGroup/Texture/pull/46) ([rewcraig](https://github.com/rewcraig)) -- Fix CustomCollectionView-Swift sample [\#22](https://github.com/TextureGroup/Texture/pull/22) ([george-gw](https://github.com/george-gw)) -- Automatically resume ASVideoNode after returning from background [\#13](https://github.com/TextureGroup/Texture/pull/13) ([plarson](https://github.com/plarson)) - -## [2.3.1](https://github.com/TextureGroup/Texture/tree/2.3.1) (2017-04-27) -[Full Changelog](https://github.com/TextureGroup/Texture/compare/2.2.1...2.3.1) - -**Merged pull requests:** - -- Don't run tests for the docs directory. [\#79](https://github.com/TextureGroup/Texture/pull/79) ([garrettmoon](https://github.com/garrettmoon)) -- Fix SCSS build [\#78](https://github.com/TextureGroup/Texture/pull/78) ([levi](https://github.com/levi)) -- Fix documentation warning on ASCollectionLayoutState.h \#trivial [\#77](https://github.com/TextureGroup/Texture/pull/77) ([garrettmoon](https://github.com/garrettmoon)) -- ASLayoutSpec to use more default implementations \#trivial [\#73](https://github.com/TextureGroup/Texture/pull/73) ([nguyenhuy](https://github.com/nguyenhuy)) -- Update the CI to the new ruby version [\#71](https://github.com/TextureGroup/Texture/pull/71) ([garrettmoon](https://github.com/garrettmoon)) -- Missing a word [\#68](https://github.com/TextureGroup/Texture/pull/68) ([djblake](https://github.com/djblake)) -- Update license v2 [\#67](https://github.com/TextureGroup/Texture/pull/67) ([garrettmoon](https://github.com/garrettmoon)) -- \[ASCollectionView\] Prevent prefetching from being enabled to eliminate overhead. [\#65](https://github.com/TextureGroup/Texture/pull/65) ([appleguy](https://github.com/appleguy)) -- \[CGPointNull\] Rename globally exported C function to avoid collisions \#trivial [\#62](https://github.com/TextureGroup/Texture/pull/62) ([appleguy](https://github.com/appleguy)) -- \[RTL\] Bridge the UISemanticContentAttribute property for more convenient RTL support. [\#60](https://github.com/TextureGroup/Texture/pull/60) ([appleguy](https://github.com/appleguy)) -- Fixes a potential deadlock; it's not safe to message likely super nod… [\#56](https://github.com/TextureGroup/Texture/pull/56) ([garrettmoon](https://github.com/garrettmoon)) -- Fix \_\_has\_include check in ASLog.h \#trivial [\#55](https://github.com/TextureGroup/Texture/pull/55) ([fsmorygo](https://github.com/fsmorygo)) -- GLKit workaround \#trivial [\#54](https://github.com/TextureGroup/Texture/pull/54) ([stephenkopylov](https://github.com/stephenkopylov)) -- Layout debugger proposal [\#52](https://github.com/TextureGroup/Texture/pull/52) ([nguyenhuy](https://github.com/nguyenhuy)) -- Fixes header check to accept the 'new' header for modified files. [\#50](https://github.com/TextureGroup/Texture/pull/50) ([garrettmoon](https://github.com/garrettmoon)) -- Remove References to IGListSectionType, Now that It's Gone [\#49](https://github.com/TextureGroup/Texture/pull/49) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Move doc stylesheets to sass [\#47](https://github.com/TextureGroup/Texture/pull/47) ([levi](https://github.com/levi)) -- Fixes for Dangerfile header checks [\#45](https://github.com/TextureGroup/Texture/pull/45) ([garrettmoon](https://github.com/garrettmoon)) -- Enforce header file changes [\#44](https://github.com/TextureGroup/Texture/pull/44) ([garrettmoon](https://github.com/garrettmoon)) -- Use \_ASCollectionReusableView inside ASIGListSupplementaryViewSourceMethods [\#40](https://github.com/TextureGroup/Texture/pull/40) ([plarson](https://github.com/plarson)) -- Bump Cartfile versions to match podspec [\#37](https://github.com/TextureGroup/Texture/pull/37) ([dymv](https://github.com/dymv)) -- \[Site\] Remove hero drop shadow [\#35](https://github.com/TextureGroup/Texture/pull/35) ([levi](https://github.com/levi)) -- Add announcement banner to documentation site [\#31](https://github.com/TextureGroup/Texture/pull/31) ([levi](https://github.com/levi)) -- \[ASCollectionLayout\] Manually set size to measured cells [\#24](https://github.com/TextureGroup/Texture/pull/24) ([nguyenhuy](https://github.com/nguyenhuy)) -- Create a Pluggable "Tips" System to Help in Development [\#19](https://github.com/TextureGroup/Texture/pull/19) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Add danger [\#18](https://github.com/TextureGroup/Texture/pull/18) ([garrettmoon](https://github.com/garrettmoon)) -- Fix Case where Network Image Node Stays Locked [\#17](https://github.com/TextureGroup/Texture/pull/17) ([Adlai-Holler](https://github.com/Adlai-Holler)) -- Update the homepage URL [\#10](https://github.com/TextureGroup/Texture/pull/10) ([garrettmoon](https://github.com/garrettmoon)) - -## [2.2.1](https://github.com/TextureGroup/Texture/tree/2.2.1) (2017-04-14) -[Full Changelog](https://github.com/TextureGroup/Texture/compare/2.3...2.2.1) - -**Merged pull requests:** - -- Add blog post [\#9](https://github.com/TextureGroup/Texture/pull/9) ([garrettmoon](https://github.com/garrettmoon)) - - - -\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* diff --git a/submodules/AsyncDisplayKit/CONTRIBUTING.md b/submodules/AsyncDisplayKit/CONTRIBUTING.md deleted file mode 100644 index 22aded2d6e..0000000000 --- a/submodules/AsyncDisplayKit/CONTRIBUTING.md +++ /dev/null @@ -1,261 +0,0 @@ -# Contribution Guidelines -Texture is the most actively developed open source project at [Pinterest](https://www.pinterest.com) and is used extensively to deliver a world-class Pinner experience. This document is setup to ensure contributing to the project is easy and transparent. - -# Questions - -If you are having difficulties using Texture or have a question about usage, please ask a -question in our [Slack channel](http://texturegroup.org/slack.html). **Please do not ask for help by filing Github issues.** - -# Core Team - -The Core Team reviews and helps iterate on the RFC Issues from the community at large and acting as the approver of these RFCs. Team members help drive Texture forward in a coherent direction consistent with the goal of creating the best possible general purpose UI framework for iOS. Team members will have merge permissions on the repository. - -Members of the core team are appointed based on their technical expertise and proven contribution to the community. The current core team members are: - -- Adlai Holler ([@](http://github.com/adlai-holler)[adlai-holler](http://github.com/adlai-holler)) -- Garrett Moon [(](https://github.com/garrettmoon)[@](https://github.com/garrettmoon)[garrett](https://github.com/garrettmoon)[moon](https://github.com/garrettmoon)) -- Huy Nguyen ([@](https://github.com/nguyenhuy)[nguyenhuy](https://github.com/nguyenhuy)) -- Michael Schneider ([@maicki](https://github.com/maicki)) -- Scott Goodson ([@appleguy](https://github.com/appleguy)) - -Over time, exceptional community members from a much more diverse background will be appointed based on their record of community involvement and contributions. - -# Issues - -Think you've found a bug or have a new feature to suggest? [Let us know](https://github.com/TextureGroup/Texture/issues/new)! - -## Where to Find Known Issues - -We use [GitHub Issues](https://github.com/texturegroup/texture/issues) for all bug tracking. We keep a close eye on this and try to make it clear when we have an internal fix in progress. Before filing a new issue, try to make sure your problem doesn't already exist. - -## Reporting New Issues -1. Update to the most recent master release if possible. We may have already fixed your bug. -2. Search for similar [issues](https://github.com/TextureGroup/Texture/issues). It's possible somebody has encountered this bug already. -3. Provide a reduced test case that specifically shows the problem. This demo should be fully operational with the exception of the bug you want to demonstrate. The more pared down, the better. If it is not possible to produce a test case, please make sure you provide very specific steps to reproduce the error. If we cannot reproduce it, and there is no other evidence to help us figure out a fix we will close the issue. -4. Your issue will be verified. The provided example will be tested for correctness. The Texture team will work with you until your issue can be verified. -5. Keep up to date with feedback from the Texture team on your issue. Your issue may be closed if it becomes stale. -6. If possible, submit a Pull Request with a failing test. Better yet, take a stab at fixing the bug yourself if you can! - -The more information you provide, the easier it is for us to validate that there is a bug and the faster we'll be able to take action. - -## Issues Triaging -- You might be requested to provide a reproduction or extra information. In that case, the issue will be labeled as **N****eeds More Info**. If we did not get any response after fourteen days, we will ping you to remind you about it. We might close the issue if we do not hear from you after two weeks since the original notice. -- If you submit a feature request as a GitHub issue, you will be invited to follow the instructions in this section otherwise the issue will be closed. -- Issues that become inactive will be labelled accordingly to inform the original poster and Texture contributors that the issue should be closed since the issue is no longer actionable. The issue can be reopened at a later time if needed, e.g. becomes actionable again. -- If possible, issues will be labeled to indicate the status or priority. For example, labels may have a prefix for Status: X, or Priority: X. Statuses may include: In Progress, On Hold. Priorities may include: P1, P2 or P3 (high to low priority). -# Requesting a Feature - -If you intend to change the public API, or make any non-trivial changes to the implementation, we recommend filing an RFC [issue](https://github.com/TextureGroup/Texture/issues/new) outlined below. This lets us reach an agreement on your proposal before you put significant effort into implementing it. - -If you're only fixing a bug, it's fine to submit a pull request right away, but we still recommend to file an issue detailing what you're fixing. This is helpful in case we don't accept that specific fix but want to keep track of the issue. - -## RFC Issue process -1. Texture has an RFC process for feature requests. To begin the discussion either gather feedback on the Texture Slack channel or draft an Texture RFC as a Github Issue. -2. The title of the GitHub RFC Issue should have `[RFC]` as prefix: `[RFC] Add new cool feature` -3. Provide a clear and detailed explanation of the feature you want and why it's important to add. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on library for Texture. -4. If the feature is complex, consider writing an Texture RFC issue. Even if you can’t implement the feature yourself, consider writing an RFC issue. If we do end up accepting the feature, the issue provides the needed documentation for contributors to develop the feature according the specification accepted by the core team. We will tag accepted RFC issues with **Needs Volunteer**. -5. After discussing the feature you may choose to attempt a Pull Request. If you're at all able, start writing some code. We always have more work to do than time to do it. If you can write some code then that will speed the process along. - -In short, if you have an idea that would be nice to have, create an issue on the [TextureGroup](https://github.com/TextureGroup/Texture)[/](https://github.com/TextureGroup/Texture)[Texture](https://github.com/TextureGroup/Texture) repo. If you have a question about requesting a feature, start a discussion in our [Slack channel](http://texturegroup.org/slack.html). - -# Our Development Process - -All work on Texture happens directly on [GitHub](https://github.com/TextureGroup/Texture). Both core team members and external contributors send pull requests which go through the same review process. - -## `master` is under active development - -We will do our best to keep master in good shape, with tests passing at all times. But in order to move fast, we will make API changes that your application might not be compatible with. We will do our best to communicate these changes and version appropriately so you can lock into a specific version if need be. - -## Pull Requests - -If you send a pull request, please do it against the master branch. We maintain stable branches for major versions separately but we don't accept pull requests to them directly. Instead, we cherry-pick non-breaking changes from master to the latest stable major version. - -Before submitting a pull request, please make sure the following is done… - -1. Search GitHub for an open or closed [pull request](https://github.com/TextureGroup/Texture/pulls?utf8=✓&q=is%3Apr) that relates to your submission. You don't want to duplicate effort. -2. Fork the [repo](https://github.com/TextureGroup/Texture) and create your branch from master: - git checkout -b my-fix-branch master -3. Create your patch, including appropriate test cases. Please follow our Coding Guidelines. -4. Please make sure every commit message are meaningful so it that makes it clearer for people to review and easier to understand your intention -5. Ensure tests pass CI on GitHub for your Pull Request. -6. If you haven't already, sign the CLA. - -**Copyright Notice for files** -Copy and paste this to the top of your new file(s): -```objc -// -// ASDisplayNode.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// -``` - -If you’ve modified an existing file, change the header to: -```objc -// -// ASDisplayNode.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// -``` - -# Semantic Versioning - -Texture follows semantic versioning. We release patch versions for bug fixes, minor versions for new features (and rarely, clear and easy to fix breaking changes), and major versions for any major breaking changes. When we make breaking changes, we also introduce deprecation warnings in a minor version so that our users learn about the upcoming changes and migrate their code in advance. - -We tag every pull request with a label marking whether the change should go in the next patch, minor, or a major version. We release new versions pretty regularly usually every few weeks. The version will be a patch or minor version if it does not contain major new features or breaking API changes and a major version if it does. - -# Coding Guidelines -- Indent using 2 spaces (this conserves space in print and makes line wrapping less likely). Never indent with tabs. Be sure to set this preference in Xcode. -- Do your best to keep it around 120 characters line length -- End files with a newline. -- Don’t leave trailing whitespace. -- Space after `@property` declarations and conform to ordering of attributes -```objc -@property (nonatomic, readonly, assign, getter=isTracking) BOOL tracking; -@property (nonatomic, readwrite, strong, nullable) NSAttributedString *attributedText; -``` -- In method signatures, there should be a space after the method type (-/+ symbol). There should be a space between the method segments (matching Apple's style). Always include a keyword and be descriptive with the word before the argument which describes the argument. -```objc -@interface SomeClass -- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height; -- (void)setExampleText:(NSString *)text image:(UIImage *)image; -- (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag; -- (id)viewWithTag:(NSInteger)tag; -@end -``` -- Internal methods should be prefixed with a `_` -```objc -- (void)_internalMethodWithParameter:(id)param; -``` -- Method braces and other braces (if/else/switch/while etc.) always open on the same line as the statement but close on a new line. -```objc -if (foo == bar) { - //.. -} else { - //.. -} -``` -- Method, `@interface` , and `@implementation` brackets on the following line -```objc -@implementation SomeClass -- (void)someMethod -{ - // Implementation -} -@end -``` -- Function brackets on the same line -```objc -static void someFunction() { - // Implementation -} -``` -- Operator goes with the variable name -```objc - NSAttributedString *attributedText = self.textNode.attributedText; -``` -- Locking - - Add a `_locked_` in front of the method name that needs to be called with a lock held -```objc -- (void)_locked_needsToBeCalledWithLock {} -``` - - Locking safety: - - It is fine for a `_locked_` method to call other `_locked_` methods. - - On the other hand, the following should not be done: - - Calling normal, unlocked methods inside a `_locked_` method - - Calling subclass hooks that are meant to be overridden by developers inside a `_locked_` method. - - Subclass hooks - - that are meant to be overwritten by users should not be called with a lock held. - - that are used internally the same conventions as above apply. -- There are multiple ways to acquire a lock: - 1. Explicitly call `.lock()` and `.unlock()` : -```objc -- (void)setContentSpacing:(CGFloat)contentSpacing -{ - __instanceLock__.lock(); - BOOL needsUpdate = (contentSpacing != _contentSpacing); - if (needsUpdate) { - _contentSpacing = contentSpacing; - } - __instanceLock__.unlock(); - - if (needsUpdate) { - [self setNeedsLayout]; - } -} - -- (CGFloat)contentSpacing -{ - CGFloat contentSpacing = 0.0; - __instanceLock__.lock(); - contentSpacing = _contentSpacing; - __instanceLock__.unlock(); - return contentSpacing; -} -``` - 2. Create an `ASDN::MutexLocker` : -```objc -- (void)setContentSpacing:(CGFloat)contentSpacing -{ - { - ASDN::MutexLocker l(__instanceLock__); - if (contentSpacing == _contentSpacing) { - return; - } - - _contentSpacing = contentSpacing; - } - - [self setNeedsLayout]; -} - -- (CGFloat)contentSpacing -{ - ASDN::MutexLocker l(__instanceLock__); - return _contentSpacing; -} -``` -- Nullability - - The adoption of annotations is straightforward. The standard we adopt is using the `NS_ASSUME_NONNULL_BEGIN` and `NS_ASSUME_NONNULL_END` on all headers. Then indicate nullability for the pointers that can be so. - - There is mostly no sense using nullability annotations outside of interface declarations. -```objc -// Properties -// Never include: `atomic`, `readwrite`, `strong`, `assign`. -// Only specify nullability if it isn't assumed from NS_ASSUME. -// (nullability, atomicity, storage class, writability, custom getter, custom setter) -@property (nullable, copy) NSNumber *status - -// Methods -- (nullable NSNumber *)doSomethingWithString:(nullable NSString *)str; - -// Functions -NSString * _Nullable ASStringWithQuotesIfMultiword(NSString * _Nullable string); - -// Typedefs -typedef void (^RemoteCallback)(id _Nullable result, NSError * _Nullable error); - -// Block as parameter -- (void)reloadDataWithCompletion:(void (^ _Nullable)())completion; - -// Block as parameter with parameter and return value -- (void)convertObject:(id _Nonnull (^ _Nullable)(id _Nullable input))handler; - -// More complex pointer types -- (void)allElementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode displaySet:(NSSet *__autoreleasing _Nullable *)displaySet preloadSet:(NSSet *__autoreleasing _Nullable *)preloadSet map:(ASElementMap *)map; -``` - -# Contributor License Agreement (CLA) - -Please sign our Contributor License Agreement (CLA) before sending pull requests. For any code changes to be accepted, the CLA must be signed. - -Complete your CLA [here](https://cla-assistant.io/TextureGroup/Texture) - -# License - -By contributing to Texture, you agree that your contributions will be licensed under its Apache 2 license. diff --git a/submodules/AsyncDisplayKit/Cartfile b/submodules/AsyncDisplayKit/Cartfile deleted file mode 100644 index ea145be2cb..0000000000 --- a/submodules/AsyncDisplayKit/Cartfile +++ /dev/null @@ -1,2 +0,0 @@ -github "pinterest/PINRemoteImage" "3.0.0-beta.14" -github "pinterest/PINCache" "3.0.1-beta.7" diff --git a/submodules/AsyncDisplayKit/Dangerfile b/submodules/AsyncDisplayKit/Dangerfile deleted file mode 100644 index 147d389069..0000000000 --- a/submodules/AsyncDisplayKit/Dangerfile +++ /dev/null @@ -1,83 +0,0 @@ -require 'open-uri' - -source_pattern = /(\.m|\.mm|\.h)$/ - -modified_source_files = git.modified_files.grep(source_pattern) -has_modified_source_files = !modified_source_files.empty? -added_source_files = git.added_files.grep(source_pattern) -has_added_source_files = !added_source_files.empty? - -# Make it more obvious that a PR is a work in progress and shouldn't be merged yet -warn("PR is classed as Work in Progress") if github.pr_title.include? "[WIP]" - -# Warn when there is a big PR -warn("This is a big PR, please consider splitting it up to ease code review.") if git.lines_of_code > 500 - -# Modifying the changelog will probably get overwritten. -if git.modified_files.include?("CHANGELOG.md") && !github.pr_title.include?("#changelog") - warn("PR modifies CHANGELOG.md, which is a generated file. Add #changelog to the title to suppress this warning.") -end - -def full_license(partial_license, filename) - license_header = <<-HEREDOC -// - HEREDOC - license_header += "// " + filename + "\n" - license_header += <<-HEREDOC -// Texture -// - HEREDOC - license_header += partial_license - return license_header -end - -def check_file_header(files_to_check, licenses) - repo_name = github.pr_json["base"]["repo"]["full_name"] - pr_number = github.pr_json["number"] - files = github.api.pull_request_files(repo_name, pr_number) - files.each do |file| - if files_to_check.include?(file["filename"]) - filename = File.basename(file["filename"]) - - data = "" - contents = github.api.get file["contents_url"] - open(contents["download_url"]) { |io| - data += io.read - } - - correct_license = false - licenses.each do |license| - license_header = full_license(license, filename) - if data.include? "Pinterest, Inc." - correct_license = true - end - end - - if correct_license == false - warn ("Please ensure license is correct for #{filename}: \n```\n" + full_license(licenses[0], filename) + "```") - end - - end - end -end - -# Ensure new files have proper header -new_source_license_header = <<-HEREDOC -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -HEREDOC - -if has_added_source_files - check_file_header(added_source_files, [new_source_license_header]) -end - -# Ensure modified files have proper header -modified_source_license_header = <<-HEREDOC -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -HEREDOC - -if has_modified_source_files - check_file_header(modified_source_files, [modified_source_license_header, new_source_license_header]) -end diff --git a/submodules/AsyncDisplayKit/Gemfile b/submodules/AsyncDisplayKit/Gemfile deleted file mode 100644 index 38bfbc8e08..0000000000 --- a/submodules/AsyncDisplayKit/Gemfile +++ /dev/null @@ -1,4 +0,0 @@ -source 'https://rubygems.org' - -gem 'danger' -gem 'danger-slack' \ No newline at end of file diff --git a/submodules/AsyncDisplayKit/InstrumentsTemplates/SystemTrace.tracetemplate b/submodules/AsyncDisplayKit/InstrumentsTemplates/SystemTrace.tracetemplate deleted file mode 100644 index 48cc3e6aef..0000000000 Binary files a/submodules/AsyncDisplayKit/InstrumentsTemplates/SystemTrace.tracetemplate and /dev/null differ diff --git a/submodules/AsyncDisplayKit/Podfile b/submodules/AsyncDisplayKit/Podfile deleted file mode 100644 index 98479737ec..0000000000 --- a/submodules/AsyncDisplayKit/Podfile +++ /dev/null @@ -1,31 +0,0 @@ -source 'https://github.com/CocoaPods/Specs.git' - -platform :ios, '9.0' - -target :'AsyncDisplayKitTests' do - pod 'OCMock', '=3.4.1' # 3.4.2 currently has issues. - pod 'FBSnapshotTestCase/Core', '~> 2.1' - pod 'JGMethodSwizzler', :git => 'https://github.com/JonasGessner/JGMethodSwizzler', :branch => 'master' - - # Only for buck build - pod 'PINRemoteImage', '3.0.0-beta.14' -end - -#TODO CocoaPods plugin instead? -post_install do |installer| - require 'fileutils' - - # Assuming we're at the root dir - buck_files_dir = 'buck-files' - if File.directory?(buck_files_dir) - installer.pod_targets.flat_map do |pod_target| - pod_name = pod_target.pod_name - # Copy the file at buck-files/BUCK_pod_name to Pods/pod_name/BUCK, - # override existing file if needed - buck_file = buck_files_dir + '/BUCK_' + pod_name - if File.file?(buck_file) - FileUtils.cp(buck_file, 'Pods/' + pod_name + '/BUCK', :preserve => false) - end - end - end -end diff --git a/submodules/AsyncDisplayKit/Podfile.lock b/submodules/AsyncDisplayKit/Podfile.lock deleted file mode 100644 index df3c6fcb88..0000000000 --- a/submodules/AsyncDisplayKit/Podfile.lock +++ /dev/null @@ -1,55 +0,0 @@ -PODS: - - FBSnapshotTestCase/Core (2.1.4) - - JGMethodSwizzler (2.0.1) - - OCMock (3.4.1) - - PINCache (3.0.1-beta.7): - - PINCache/Arc-exception-safe (= 3.0.1-beta.7) - - PINCache/Core (= 3.0.1-beta.7) - - PINCache/Arc-exception-safe (3.0.1-beta.7): - - PINCache/Core - - PINCache/Core (3.0.1-beta.7): - - PINOperation (~> 1.1.1) - - PINOperation (1.1.1) - - PINRemoteImage (3.0.0-beta.14): - - PINRemoteImage/PINCache (= 3.0.0-beta.14) - - PINRemoteImage/Core (3.0.0-beta.14): - - PINOperation - - PINRemoteImage/PINCache (3.0.0-beta.14): - - PINCache (= 3.0.1-beta.7) - - PINRemoteImage/Core - -DEPENDENCIES: - - FBSnapshotTestCase/Core (~> 2.1) - - JGMethodSwizzler (from `https://github.com/JonasGessner/JGMethodSwizzler`, branch `master`) - - OCMock (= 3.4.1) - - PINRemoteImage (= 3.0.0-beta.14) - -SPEC REPOS: - https://github.com/cocoapods/specs.git: - - FBSnapshotTestCase - - OCMock - - PINCache - - PINOperation - - PINRemoteImage - -EXTERNAL SOURCES: - JGMethodSwizzler: - :branch: master - :git: https://github.com/JonasGessner/JGMethodSwizzler - -CHECKOUT OPTIONS: - JGMethodSwizzler: - :commit: 8791eccc5342224bd293b5867348657e3a240c7f - :git: https://github.com/JonasGessner/JGMethodSwizzler - -SPEC CHECKSUMS: - FBSnapshotTestCase: 094f9f314decbabe373b87cc339bea235a63e07a - JGMethodSwizzler: 7328146117fffa8a4038c42eb7cd3d4c75006f97 - OCMock: 2cd0716969bab32a2283ff3a46fd26a8c8b4c5e3 - PINCache: 7cb9ae068c8f655717f7c644ef1dff9fd573e979 - PINOperation: a6219e6fc9db9c269eb7a7b871ac193bcf400aac - PINRemoteImage: 81bbff853acc71c6de9e106e9e489a791b8bbb08 - -PODFILE CHECKSUM: 445046ac151568c694ff286684322273f0b597d6 - -COCOAPODS: 1.6.0 diff --git a/submodules/AsyncDisplayKit/README.md b/submodules/AsyncDisplayKit/README.md deleted file mode 100644 index 4b939fac5a..0000000000 --- a/submodules/AsyncDisplayKit/README.md +++ /dev/null @@ -1,51 +0,0 @@ -## Coming from AsyncDisplayKit? Learn more [here](https://medium.com/@Pinterest_Engineering/introducing-texture-a-new-home-for-asyncdisplaykit-e7c003308f50) - -![Texture](https://github.com/texturegroup/texture/raw/master/docs/static/images/logo.png) - -[![Apps Using](https://img.shields.io/cocoapods/at/Texture.svg?label=Apps%20Using%20Texture&colorB=28B9FE)](http://cocoapods.org/pods/Texture) -[![Downloads](https://img.shields.io/cocoapods/dt/Texture.svg?label=Total%20Downloads&colorB=28B9FE)](http://cocoapods.org/pods/Texture) - -[![Platform](https://img.shields.io/badge/platforms-iOS%20%7C%20tvOS-orange.svg)](http://texturegroup.org) -[![Languages](https://img.shields.io/badge/languages-ObjC%20%7C%20Swift-orange.svg)](http://texturegroup.org) - -[![Version](https://img.shields.io/cocoapods/v/Texture.svg)](http://cocoapods.org/pods/Texture) -[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-59C939.svg?style=flat)](https://github.com/Carthage/Carthage) -[![License](https://img.shields.io/cocoapods/l/Texture.svg)](https://github.com/texturegroup/texture/blob/master/LICENSE) - -## Installation - -Texture is available via CocoaPods or Carthage. See our [Installation](http://texturegroup.org/docs/installation.html) guide for instructions. - -## Performance Gains - -Texture's basic unit is the `node`. An ASDisplayNode is an abstraction over `UIView`, which in turn is an abstraction over `CALayer`. Unlike views, which can only be used on the main thread, nodes are thread-safe: you can instantiate and configure entire hierarchies of them in parallel on background threads. - -To keep its user interface smooth and responsive, your app should render at 60 frames per second — the gold standard on iOS. This means the main thread has one-sixtieth of a second to push each frame. That's 16 milliseconds to execute all layout and drawing code! And because of system overhead, your code usually has less than ten milliseconds to run before it causes a frame drop. - -Texture lets you move image decoding, text sizing and rendering, layout, and other expensive UI operations off the main thread, to keep the main thread available to respond to user interaction. - -## Advanced Developer Features - -As the framework has grown, many features have been added that can save developers tons of time by eliminating common boilerplate style structures common in modern iOS apps. If you've ever dealt with cell reuse bugs, tried to performantly preload data for a page or scroll style interface or even just tried to keep your app from dropping too many frames you can benefit from integrating Texture. - -## Learn More - -* Read the our [Getting Started](http://texturegroup.org/docs/getting-started.html) guide -* Get the [sample projects](https://github.com/texturegroup/texture/tree/master/examples) -* Browse the [API reference](http://texturegroup.org/appledocs.html) - -## Getting Help - -We use Slack for real-time debugging, community updates, and general talk about Texture. [Signup](http://asdk-slack-auto-invite.herokuapp.com) yourself or email textureframework@gmail.com to get an invite. - -## Release process - -For the release process see the [RELEASE] (https://github.com/texturegroup/texture/blob/master/RELEASE.md) file. - -## Contributing - -We welcome any contributions. See the [CONTRIBUTING](https://github.com/texturegroup/texture/blob/master/CONTRIBUTING.md) file for how to get involved. - -## License - -The Texture project is available for free use, as described by the [LICENSE](https://github.com/texturegroup/texture/blob/master/LICENSE) (Apache 2.0). diff --git a/submodules/AsyncDisplayKit/RELEASE.md b/submodules/AsyncDisplayKit/RELEASE.md deleted file mode 100644 index b675fa4922..0000000000 --- a/submodules/AsyncDisplayKit/RELEASE.md +++ /dev/null @@ -1,15 +0,0 @@ -# Release Process -This document describes the process for a public Texture release. - -### Preparation -- Install [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator): `sudo gem install github_changelog_generator` -- Generate a GitHub Personal Access Token to prevent running into public GitHub API rate limits: https://github.com/github-changelog-generator/github-changelog-generator#github-token - -### Process -- Run `github_changelog_generator` in Texture project directory: `github_changelog_generator --token TextureGroup/Texture`. To avoid hitting rate limit, the generator will replace the entire file with just the changes from this version – revert that giant deletion to get the entire new changelog. -- Update `spec.version` within `Texture.podspec` and the `since-tag` and `future-release` fields in `.github_changelog_generator`. -- Create a new PR with the updated `Texture.podspec` and the newly generated changelog, add `#changelog` to the PR message so the CI will not prevent merging it. -- After merging in the PR, [create a new GitHub release](https://github.com/TextureGroup/Texture/releases/new). Use the generated changelog for the new release. - -### Problems -- Sometimes we will still run into GitHub rate limit issues although using a personal token to generate the changelog. For now there is no solution for this. The issue to track is: [Triggering Github Rate limits #656](https://github.com/github-changelog-generator/github-changelog-generator/issues/656) \ No newline at end of file diff --git a/submodules/AsyncDisplayKit/Schemas/configuration.json b/submodules/AsyncDisplayKit/Schemas/configuration.json deleted file mode 100644 index 12957a12b5..0000000000 --- a/submodules/AsyncDisplayKit/Schemas/configuration.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "id": "configuration.json", - "title": "configuration", - "description" : "Schema definition of a Texture Configuration", - "$schema": "http://json-schema.org/schema#", - "type": "object", - "properties": { - "version" : { - "type" : "number" - }, - "experimental_features": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "exp_graphics_contexts", - "exp_text_node", - "exp_interface_state_coalesce", - "exp_unfair_lock", - "exp_infer_layer_defaults", - "exp_collection_teardown", - "exp_framesetter_cache", - "exp_skip_clear_data", - "exp_did_enter_preload_skip_asm_layout", - "exp_disable_a11y_cache", - "exp_dispatch_apply", - "exp_image_downloader_priority", - "exp_text_drawing" - ] - } - } - } -} diff --git a/submodules/AsyncDisplayKit/Source/Private/ASAbstractLayoutController+FrameworkPrivate.h b/submodules/AsyncDisplayKit/Source/ASAbstractLayoutController+FrameworkPrivate.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/Private/ASAbstractLayoutController+FrameworkPrivate.h rename to submodules/AsyncDisplayKit/Source/ASAbstractLayoutController+FrameworkPrivate.h diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASAsciiArtBoxCreator.mm b/submodules/AsyncDisplayKit/Source/ASAsciiArtBoxCreator.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/Layout/ASAsciiArtBoxCreator.mm rename to submodules/AsyncDisplayKit/Source/ASAsciiArtBoxCreator.mm diff --git a/submodules/AsyncDisplayKit/Source/Base/ASAssert.mm b/submodules/AsyncDisplayKit/Source/ASAssert.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/Base/ASAssert.mm rename to submodules/AsyncDisplayKit/Source/ASAssert.mm diff --git a/submodules/AsyncDisplayKit/Source/ASButtonNode+Private.h b/submodules/AsyncDisplayKit/Source/ASButtonNode+Private.h deleted file mode 100644 index 2ecc894463..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASButtonNode+Private.h +++ /dev/null @@ -1,44 +0,0 @@ -// -// ASButtonNode+Private.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import -#import - -@interface ASButtonNode() { - NSAttributedString *_normalAttributedTitle; - NSAttributedString *_highlightedAttributedTitle; - NSAttributedString *_selectedAttributedTitle; - NSAttributedString *_selectedHighlightedAttributedTitle; - NSAttributedString *_disabledAttributedTitle; - - UIImage *_normalImage; - UIImage *_highlightedImage; - UIImage *_selectedImage; - UIImage *_selectedHighlightedImage; - UIImage *_disabledImage; - - UIImage *_normalBackgroundImage; - UIImage *_highlightedBackgroundImage; - UIImage *_selectedBackgroundImage; - UIImage *_selectedHighlightedBackgroundImage; - UIImage *_disabledBackgroundImage; - - CGFloat _contentSpacing; - BOOL _laysOutHorizontally; - ASVerticalAlignment _contentVerticalAlignment; - ASHorizontalAlignment _contentHorizontalAlignment; - UIEdgeInsets _contentEdgeInsets; - ASButtonNodeImageAlignment _imageAlignment; - ASTextNode *_titleNode; - ASImageNode *_imageNode; - ASImageNode *_backgroundImageNode; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASButtonNode+Yoga.h b/submodules/AsyncDisplayKit/Source/ASButtonNode+Yoga.h deleted file mode 100644 index 4fddc9df3a..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASButtonNode+Yoga.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// ASButtonNode+Yoga.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface ASButtonNode (Yoga) - -- (void)updateYogaLayoutIfNeeded; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASButtonNode+Yoga.mm b/submodules/AsyncDisplayKit/Source/ASButtonNode+Yoga.mm deleted file mode 100644 index 4666475560..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASButtonNode+Yoga.mm +++ /dev/null @@ -1,106 +0,0 @@ -// -// ASButtonNode+Yoga.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import "ASButtonNode+Yoga.h" -#import -#import -#import -#import - -#if YOGA -static void ASButtonNodeResolveHorizontalAlignmentForStyle(ASLayoutElementStyle *style, ASStackLayoutDirection _direction, ASHorizontalAlignment _horizontalAlignment, ASStackLayoutJustifyContent _justifyContent, ASStackLayoutAlignItems _alignItems) { - if (_direction == ASStackLayoutDirectionHorizontal) { - style.justifyContent = justifyContent(_horizontalAlignment, _justifyContent); - } else { - style.alignItems = alignment(_horizontalAlignment, _alignItems); - } -} - -static void ASButtonNodeResolveVerticalAlignmentForStyle(ASLayoutElementStyle *style, ASStackLayoutDirection _direction, ASVerticalAlignment _verticalAlignment, ASStackLayoutJustifyContent _justifyContent, ASStackLayoutAlignItems _alignItems) { - if (_direction == ASStackLayoutDirectionHorizontal) { - style.alignItems = alignment(_verticalAlignment, _alignItems); - } else { - style.justifyContent = justifyContent(_verticalAlignment, _justifyContent); - } -} - -@implementation ASButtonNode (Yoga) - -- (void)updateYogaLayoutIfNeeded -{ - NSMutableArray *children = [[NSMutableArray alloc] initWithCapacity:2]; - { - ASLockScopeSelf(); - - // Build up yoga children for button node again - unowned ASLayoutElementStyle *style = [self _locked_style]; - [style yogaNodeCreateIfNeeded]; - - // Setup stack layout values - style.flexDirection = _laysOutHorizontally ? ASStackLayoutDirectionHorizontal : ASStackLayoutDirectionVertical; - - // Resolve horizontal and vertical alignment - ASButtonNodeResolveHorizontalAlignmentForStyle(style, style.flexDirection, _contentHorizontalAlignment, style.justifyContent, style.alignItems); - ASButtonNodeResolveVerticalAlignmentForStyle(style, style.flexDirection, _contentVerticalAlignment, style.justifyContent, style.alignItems); - - // Setup new yoga children - if (_imageNode.image != nil) { - [_imageNode.style yogaNodeCreateIfNeeded]; - [children addObject:_imageNode]; - } - - if (_titleNode.attributedText.length > 0) { - [_titleNode.style yogaNodeCreateIfNeeded]; - if (_imageAlignment == ASButtonNodeImageAlignmentBeginning) { - [children addObject:_titleNode]; - } else { - [children insertObject:_titleNode atIndex:0]; - } - } - - // Add spacing between title and button - if (children.count == 2) { - unowned ASLayoutElementStyle *firstChildStyle = children.firstObject.style; - if (_laysOutHorizontally) { - firstChildStyle.margin = ASEdgeInsetsMake(UIEdgeInsetsMake(0, 0, 0, _contentSpacing)); - } else { - firstChildStyle.margin = ASEdgeInsetsMake(UIEdgeInsetsMake(0, 0, _contentSpacing, 0)); - } - } - - // Add padding to button - if (UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, _contentEdgeInsets) == NO) { - style.padding = ASEdgeInsetsMake(_contentEdgeInsets); - } - - // Add background node - if (_backgroundImageNode.image) { - [_backgroundImageNode.style yogaNodeCreateIfNeeded]; - [children insertObject:_backgroundImageNode atIndex:0]; - - _backgroundImageNode.style.positionType = YGPositionTypeAbsolute; - _backgroundImageNode.style.position = ASEdgeInsetsMake(UIEdgeInsetsZero); - } - } - - // Update new children - [self setYogaChildren:children]; -} - -@end - -#else - -@implementation ASButtonNode (Yoga) - -- (void)updateYogaLayoutIfNeeded {} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASButtonNode.h b/submodules/AsyncDisplayKit/Source/ASButtonNode.h deleted file mode 100644 index f43a544df1..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASButtonNode.h +++ /dev/null @@ -1,130 +0,0 @@ -// -// ASButtonNode.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@class ASImageNode, ASTextNode; - -/** - Image alignment defines where the image will be placed relative to the text. - */ -typedef NS_ENUM(NSInteger, ASButtonNodeImageAlignment) { - /** Places the image before the text. */ - ASButtonNodeImageAlignmentBeginning, - /** Places the image after the text. */ - ASButtonNodeImageAlignmentEnd -}; - -@interface ASButtonNode : ASControlNode - -@property (readonly) ASTextNode * titleNode; -@property (readonly) ASImageNode * imageNode; -@property (readonly) ASImageNode * backgroundImageNode; - -/** - Spacing between image and title. Defaults to 8.0. - */ -@property CGFloat contentSpacing; - -/** - Whether button should be laid out vertically (image on top of text) or horizontally (image to the left of text). - ASButton node does not yet support RTL but it should be fairly easy to implement. - Defaults to YES. - */ -@property BOOL laysOutHorizontally; - -/** Horizontally align content (text or image). - Defaults to ASHorizontalAlignmentMiddle. - */ -@property ASHorizontalAlignment contentHorizontalAlignment; - -/** Vertically align content (text or image). - Defaults to ASVerticalAlignmentCenter. - */ -@property ASVerticalAlignment contentVerticalAlignment; - -/** - * @discussion The insets used around the title and image node - */ -@property UIEdgeInsets contentEdgeInsets; - -/** - * @discusstion Whether the image should be aligned at the beginning or at the end of node. Default is `ASButtonNodeImageAlignmentBeginning`. - */ -@property ASButtonNodeImageAlignment imageAlignment; - -/** - * Returns the styled title associated with the specified state. - * - * @param state The control state that uses the styled title. - * - * @return The title for the specified state. - */ -- (nullable NSAttributedString *)attributedTitleForState:(UIControlState)state AS_WARN_UNUSED_RESULT; - -/** - * Sets the styled title to use for the specified state. This will reset styled title previously set with -setTitle:withFont:withColor:forState. - * - * @param title The styled text string to use for the title. - * @param state The control state that uses the specified title. - */ -- (void)setAttributedTitle:(nullable NSAttributedString *)title forState:(UIControlState)state; - -#if TARGET_OS_IOS -/** - * Sets the title to use for the specified state. This will reset styled title previously set with -setAttributedTitle:forState. - * - * @param title The styled text string to use for the title. - * @param font The font to use for the title. - * @param color The color to use for the title. - * @param state The control state that uses the specified title. - */ -- (void)setTitle:(NSString *)title withFont:(nullable UIFont *)font withColor:(nullable UIColor *)color forState:(UIControlState)state; -#endif -/** - * Returns the image used for a button state. - * - * @param state The control state that uses the image. - * - * @return The image used for the specified state. - */ -- (nullable UIImage *)imageForState:(UIControlState)state AS_WARN_UNUSED_RESULT; - -/** - * Sets the image to use for the specified state. - * - * @param image The image to use for the specified state. - * @param state The control state that uses the specified title. - */ -- (void)setImage:(nullable UIImage *)image forState:(UIControlState)state; - -/** - * Sets the background image to use for the specified state. - * - * @param image The image to use for the specified state. - * @param state The control state that uses the specified title. - */ -- (void)setBackgroundImage:(nullable UIImage *)image forState:(UIControlState)state; - - -/** - * Returns the background image used for a button state. - * - * @param state The control state that uses the image. - * - * @return The background image used for the specified state. - */ -- (nullable UIImage *)backgroundImageForState:(UIControlState)state AS_WARN_UNUSED_RESULT; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASButtonNode.mm b/submodules/AsyncDisplayKit/Source/ASButtonNode.mm deleted file mode 100644 index 828996bc9f..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASButtonNode.mm +++ /dev/null @@ -1,561 +0,0 @@ -// -// ASButtonNode.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import -#import -#import -#import -#import -#import -#import - -@implementation ASButtonNode - -@synthesize contentSpacing = _contentSpacing; -@synthesize laysOutHorizontally = _laysOutHorizontally; -@synthesize contentVerticalAlignment = _contentVerticalAlignment; -@synthesize contentHorizontalAlignment = _contentHorizontalAlignment; -@synthesize contentEdgeInsets = _contentEdgeInsets; -@synthesize imageAlignment = _imageAlignment; -@synthesize titleNode = _titleNode; -@synthesize imageNode = _imageNode; -@synthesize backgroundImageNode = _backgroundImageNode; - -#pragma mark - Lifecycle - -- (instancetype)init -{ - if (self = [super init]) { - self.automaticallyManagesSubnodes = YES; - - _contentSpacing = 8.0; - _laysOutHorizontally = YES; - _contentHorizontalAlignment = ASHorizontalAlignmentMiddle; - _contentVerticalAlignment = ASVerticalAlignmentCenter; - _contentEdgeInsets = UIEdgeInsetsZero; - _imageAlignment = ASButtonNodeImageAlignmentBeginning; - self.accessibilityTraits = self.defaultAccessibilityTraits; - - [self updateYogaLayoutIfNeeded]; - } - return self; -} - -- (ASTextNode *)titleNode -{ - ASLockScopeSelf(); - if (!_titleNode) { - _titleNode = [[ASTextNode alloc] init]; -#if TARGET_OS_IOS - // tvOS needs access to the underlying view - // of the button node to add a touch handler. - [_titleNode setLayerBacked:YES]; -#endif - _titleNode.style.flexShrink = 1.0; - } - return _titleNode; -} - -#pragma mark - Public Getter - -- (ASImageNode *)imageNode -{ - ASLockScopeSelf(); - if (!_imageNode) { - _imageNode = [[ASImageNode alloc] init]; - _imageNode.displayWithoutProcessing = true; - _imageNode.displaysAsynchronously = false; - [_imageNode setLayerBacked:YES]; - } - return _imageNode; -} - -- (ASImageNode *)backgroundImageNode -{ - ASLockScopeSelf(); - if (!_backgroundImageNode) { - _backgroundImageNode = [[ASImageNode alloc] init]; - _backgroundImageNode.displayWithoutProcessing = true; - _backgroundImageNode.displaysAsynchronously = false; - [_backgroundImageNode setLayerBacked:YES]; - [_backgroundImageNode setContentMode:UIViewContentModeScaleToFill]; - } - return _backgroundImageNode; -} - -- (void)setLayerBacked:(BOOL)layerBacked -{ - ASDisplayNodeAssert(!layerBacked, @"ASButtonNode must not be layer backed!"); - [super setLayerBacked:layerBacked]; -} - -- (void)setEnabled:(BOOL)enabled -{ - if (self.enabled != enabled) { - [super setEnabled:enabled]; - self.accessibilityTraits = self.defaultAccessibilityTraits; - [self updateButtonContent]; - } -} - -- (void)setHighlighted:(BOOL)highlighted -{ - if (self.highlighted != highlighted) { - [super setHighlighted:highlighted]; - [self updateButtonContent]; - } -} - -- (void)setSelected:(BOOL)selected -{ - if (self.selected != selected) { - [super setSelected:selected]; - [self updateButtonContent]; - } -} - -- (void)updateButtonContent -{ - [self updateBackgroundImage]; - [self updateImage]; - [self updateTitle]; -} - -- (void)setDisplaysAsynchronously:(BOOL)displaysAsynchronously -{ - [super setDisplaysAsynchronously:displaysAsynchronously]; - [self.backgroundImageNode setDisplaysAsynchronously:displaysAsynchronously]; - [self.imageNode setDisplaysAsynchronously:displaysAsynchronously]; - [self.titleNode setDisplaysAsynchronously:displaysAsynchronously]; -} - -- (void)updateImage -{ - [self lock]; - - UIImage *newImage; - if (self.enabled == NO && _disabledImage) { - newImage = _disabledImage; - } else if (self.highlighted && self.selected && _selectedHighlightedImage) { - newImage = _selectedHighlightedImage; - } else if (self.highlighted && _highlightedImage) { - newImage = _highlightedImage; - } else if (self.selected && _selectedImage) { - newImage = _selectedImage; - } else { - newImage = _normalImage; - } - - if ((_imageNode != nil || newImage != nil) && newImage != self.imageNode.image) { - _imageNode.image = newImage; - [self unlock]; - - [self updateYogaLayoutIfNeeded]; - [self setNeedsLayout]; - return; - } - - [self unlock]; -} - -- (void)updateTitle -{ - [self lock]; - - NSAttributedString *newTitle; - if (self.enabled == NO && _disabledAttributedTitle) { - newTitle = _disabledAttributedTitle; - } else if (self.highlighted && self.selected && _selectedHighlightedAttributedTitle) { - newTitle = _selectedHighlightedAttributedTitle; - } else if (self.highlighted && _highlightedAttributedTitle) { - newTitle = _highlightedAttributedTitle; - } else if (self.selected && _selectedAttributedTitle) { - newTitle = _selectedAttributedTitle; - } else { - newTitle = _normalAttributedTitle; - } - - // Calling self.titleNode is essential here because _titleNode is lazily created by the getter. - if ((_titleNode != nil || newTitle.length > 0) && [self.titleNode.attributedText isEqualToAttributedString:newTitle] == NO) { - _titleNode.attributedText = newTitle; - [self unlock]; - - self.accessibilityLabel = self.defaultAccessibilityLabel; - [self updateYogaLayoutIfNeeded]; - [self setNeedsLayout]; - return; - } - - [self unlock]; -} - -- (void)updateBackgroundImage -{ - [self lock]; - - UIImage *newImage; - if (self.enabled == NO && _disabledBackgroundImage) { - newImage = _disabledBackgroundImage; - } else if (self.highlighted && self.selected && _selectedHighlightedBackgroundImage) { - newImage = _selectedHighlightedBackgroundImage; - } else if (self.highlighted && _highlightedBackgroundImage) { - newImage = _highlightedBackgroundImage; - } else if (self.selected && _selectedBackgroundImage) { - newImage = _selectedBackgroundImage; - } else { - newImage = _normalBackgroundImage; - } - - if ((_backgroundImageNode != nil || newImage != nil) && newImage != self.backgroundImageNode.image) { - _backgroundImageNode.image = newImage; - [self unlock]; - - [self updateYogaLayoutIfNeeded]; - [self setNeedsLayout]; - return; - } - - [self unlock]; -} - -- (CGFloat)contentSpacing -{ - ASLockScopeSelf(); - return _contentSpacing; -} - -- (void)setContentSpacing:(CGFloat)contentSpacing -{ - if (ASLockedSelfCompareAssign(_contentSpacing, contentSpacing)) { - [self updateYogaLayoutIfNeeded]; - [self setNeedsLayout]; - } -} - -- (BOOL)laysOutHorizontally -{ - ASLockScopeSelf(); - return _laysOutHorizontally; -} - -- (void)setLaysOutHorizontally:(BOOL)laysOutHorizontally -{ - if (ASLockedSelfCompareAssign(_laysOutHorizontally, laysOutHorizontally)) { - [self updateYogaLayoutIfNeeded]; - [self setNeedsLayout]; - } -} - -- (ASVerticalAlignment)contentVerticalAlignment -{ - ASLockScopeSelf(); - return _contentVerticalAlignment; -} - -- (void)setContentVerticalAlignment:(ASVerticalAlignment)contentVerticalAlignment -{ - ASLockScopeSelf(); - _contentVerticalAlignment = contentVerticalAlignment; -} - -- (ASHorizontalAlignment)contentHorizontalAlignment -{ - ASLockScopeSelf(); - return _contentHorizontalAlignment; -} - -- (void)setContentHorizontalAlignment:(ASHorizontalAlignment)contentHorizontalAlignment -{ - ASLockScopeSelf(); - _contentHorizontalAlignment = contentHorizontalAlignment; -} - -- (UIEdgeInsets)contentEdgeInsets -{ - ASLockScopeSelf(); - return _contentEdgeInsets; -} - -- (void)setContentEdgeInsets:(UIEdgeInsets)contentEdgeInsets -{ - ASLockScopeSelf(); - _contentEdgeInsets = contentEdgeInsets; -} - -- (ASButtonNodeImageAlignment)imageAlignment -{ - ASLockScopeSelf(); - return _imageAlignment; -} - -- (void)setImageAlignment:(ASButtonNodeImageAlignment)imageAlignment -{ - ASLockScopeSelf(); - _imageAlignment = imageAlignment; -} - - -#if TARGET_OS_IOS -- (void)setTitle:(NSString *)title withFont:(UIFont *)font withColor:(UIColor *)color forState:(UIControlState)state -{ - NSDictionary *attributes = @{ - NSFontAttributeName: font ? : [UIFont systemFontOfSize:[UIFont buttonFontSize]], - NSForegroundColorAttributeName : color ? : [UIColor blackColor] - }; - - NSAttributedString *string = [[NSAttributedString alloc] initWithString:title attributes:attributes]; - [self setAttributedTitle:string forState:state]; -} -#endif - -- (NSAttributedString *)attributedTitleForState:(UIControlState)state -{ - ASLockScopeSelf(); - switch (state) { - case UIControlStateNormal: - return _normalAttributedTitle; - - case UIControlStateHighlighted: - return _highlightedAttributedTitle; - - case UIControlStateSelected: - return _selectedAttributedTitle; - - case UIControlStateSelected | UIControlStateHighlighted: - return _selectedHighlightedAttributedTitle; - - case UIControlStateDisabled: - return _disabledAttributedTitle; - - default: - return _normalAttributedTitle; - } -} - -- (void)setAttributedTitle:(NSAttributedString *)title forState:(UIControlState)state -{ - { - ASLockScopeSelf(); - switch (state) { - case UIControlStateNormal: - _normalAttributedTitle = [title copy]; - break; - - case UIControlStateHighlighted: - _highlightedAttributedTitle = [title copy]; - break; - - case UIControlStateSelected: - _selectedAttributedTitle = [title copy]; - break; - - case UIControlStateSelected | UIControlStateHighlighted: - _selectedHighlightedAttributedTitle = [title copy]; - break; - - case UIControlStateDisabled: - _disabledAttributedTitle = [title copy]; - break; - - default: - break; - } - } - - [self updateTitle]; -} - -- (UIImage *)imageForState:(UIControlState)state -{ - ASLockScopeSelf(); - switch (state) { - case UIControlStateNormal: - return _normalImage; - - case UIControlStateHighlighted: - return _highlightedImage; - - case UIControlStateSelected: - return _selectedImage; - - case UIControlStateSelected | UIControlStateHighlighted: - return _selectedHighlightedImage; - - case UIControlStateDisabled: - return _disabledImage; - - default: - return _normalImage; - } -} - -- (void)setImage:(UIImage *)image forState:(UIControlState)state -{ - { - ASLockScopeSelf(); - switch (state) { - case UIControlStateNormal: - _normalImage = image; - break; - - case UIControlStateHighlighted: - _highlightedImage = image; - break; - - case UIControlStateSelected: - _selectedImage = image; - break; - - case UIControlStateSelected | UIControlStateHighlighted: - _selectedHighlightedImage = image; - break; - - case UIControlStateDisabled: - _disabledImage = image; - break; - - default: - break; - } - } - - [self updateImage]; -} - -- (UIImage *)backgroundImageForState:(UIControlState)state -{ - ASLockScopeSelf(); - switch (state) { - case UIControlStateNormal: - return _normalBackgroundImage; - - case UIControlStateHighlighted: - return _highlightedBackgroundImage; - - case UIControlStateSelected: - return _selectedBackgroundImage; - - case UIControlStateSelected | UIControlStateHighlighted: - return _selectedHighlightedBackgroundImage; - - case UIControlStateDisabled: - return _disabledBackgroundImage; - - default: - return _normalBackgroundImage; - } -} - -- (void)setBackgroundImage:(UIImage *)image forState:(UIControlState)state -{ - { - ASLockScopeSelf(); - switch (state) { - case UIControlStateNormal: - _normalBackgroundImage = image; - break; - - case UIControlStateHighlighted: - _highlightedBackgroundImage = image; - break; - - case UIControlStateSelected: - _selectedBackgroundImage = image; - break; - - case UIControlStateSelected | UIControlStateHighlighted: - _selectedHighlightedBackgroundImage = image; - break; - - case UIControlStateDisabled: - _disabledBackgroundImage = image; - break; - - default: - break; - } - } - - [self updateBackgroundImage]; -} - - -- (NSString *)defaultAccessibilityLabel -{ - ASLockScopeSelf(); - return _titleNode.defaultAccessibilityLabel; -} - -- (UIAccessibilityTraits)defaultAccessibilityTraits -{ - return self.enabled ? UIAccessibilityTraitButton - : (UIAccessibilityTraitButton | UIAccessibilityTraitNotEnabled); -} - -#pragma mark - Layout - -#if !YOGA -- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize -{ - UIEdgeInsets contentEdgeInsets; - ASButtonNodeImageAlignment imageAlignment; - ASLayoutSpec *spec; - ASStackLayoutSpec *stack = [[ASStackLayoutSpec alloc] init]; - { - ASLockScopeSelf(); - stack.direction = _laysOutHorizontally ? ASStackLayoutDirectionHorizontal : ASStackLayoutDirectionVertical; - stack.spacing = _contentSpacing; - stack.horizontalAlignment = _contentHorizontalAlignment; - stack.verticalAlignment = _contentVerticalAlignment; - - contentEdgeInsets = _contentEdgeInsets; - imageAlignment = _imageAlignment; - } - - NSMutableArray *children = [[NSMutableArray alloc] initWithCapacity:2]; - if (_imageNode.image) { - [children addObject:_imageNode]; - } - - if (_titleNode.attributedText.length > 0) { - if (imageAlignment == ASButtonNodeImageAlignmentBeginning) { - [children addObject:_titleNode]; - } else { - [children insertObject:_titleNode atIndex:0]; - } - } - - stack.children = children; - - spec = stack; - - if (UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, contentEdgeInsets) == NO) { - spec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:contentEdgeInsets child:spec]; - } - - if (_backgroundImageNode.image) { - spec = [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:spec background:_backgroundImageNode]; - } - - return spec; -} -#endif - -- (void)layout -{ - [super layout]; - - _backgroundImageNode.hidden = (_backgroundImageNode.image == nil); - _imageNode.hidden = (_imageNode.image == nil); - _titleNode.hidden = (_titleNode.attributedText.length == 0); -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASCellNode.h b/submodules/AsyncDisplayKit/Source/ASCellNode.h deleted file mode 100644 index 35d836ce5f..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCellNode.h +++ /dev/null @@ -1,263 +0,0 @@ -// -// ASCellNode.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -NS_ASSUME_NONNULL_BEGIN - -@class ASCellNode, ASTextNode; -@protocol ASRangeManagingNode; - -typedef NSUInteger ASCellNodeAnimation; - -typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) { - /** - * Indicates a cell has just became visible - */ - ASCellNodeVisibilityEventVisible, - /** - * Its position (determined by scrollView.contentOffset) has changed while at least 1px remains visible. - * It is possible that 100% of the cell is visible both before and after and only its position has changed, - * or that the position change has resulted in more or less of the cell being visible. - * Use CGRectIntersect between cellFrame and scrollView.bounds to get this rectangle - */ - ASCellNodeVisibilityEventVisibleRectChanged, - /** - * Indicates a cell is no longer visible - */ - ASCellNodeVisibilityEventInvisible, - /** - * Indicates user has started dragging the visible cell - */ - ASCellNodeVisibilityEventWillBeginDragging, - /** - * Indicates user has ended dragging the visible cell - */ - ASCellNodeVisibilityEventDidEndDragging, -}; - -/** - * Generic cell node. Subclass this instead of `ASDisplayNode` to use with `ASTableView` and `ASCollectionView`. - - * @note When a cell node is contained inside a collection view (or table view), - * calling `-setNeedsLayout` will also notify the collection on the main thread - * so that the collection can update its item layout if the cell's size changed. - */ -@interface ASCellNode : ASDisplayNode - -/** - * @abstract When enabled, ensures that the cell is completely displayed before allowed onscreen. - * - * @default NO - * @discussion Normally, ASCellNodes are preloaded and have finished display before they are onscreen. - * However, if the Table or Collection's rangeTuningParameters are set to small values (or 0), - * or if the user is scrolling rapidly on a slow device, it is possible for a cell's display to - * be incomplete when it becomes visible. - * - * In this case, normally placeholder states are shown and scrolling continues uninterrupted. - * The finished, drawn content is then shown as soon as it is ready. - * - * With this property set to YES, the main thread will be blocked until display is complete for - * the cell. This is more similar to UIKit, and in fact makes AsyncDisplayKit scrolling visually - * indistinguishable from UIKit's, except being faster. - * - * Using this option does not eliminate all of the performance advantages of AsyncDisplayKit. - * Normally, a cell has been preloading and is almost done when it reaches the screen, so the - * blocking time is very short. If the rangeTuningParameters are set to 0, still this option - * outperforms UIKit: while the main thread is waiting, subnode display executes concurrently. - */ -@property BOOL neverShowPlaceholders; - -/* - * The kind of supplementary element this node represents, if any. - * - * @return The supplementary element kind, or @c nil if this node does not represent a supplementary element. - */ -@property (nullable, copy, readonly) NSString *supplementaryElementKind; - -/* - * The layout attributes currently assigned to this node, if any. - * - * @discussion This property is useful because it is set before @c collectionView:willDisplayNode:forItemAtIndexPath: - * is called, when the node is not yet in the hierarchy and its frame cannot be converted to/from other nodes. Instead - * you can use the layout attributes object to learn where and how the cell will be displayed. - */ -@property (nullable, copy, readonly) UICollectionViewLayoutAttributes *layoutAttributes; - -/** - * A Boolean value that is synchronized with the underlying collection or tableView cell property. - * Setting this value is equivalent to calling selectItem / deselectItem on the collection or table. - */ -@property (getter=isSelected) BOOL selected; - -/** - * A Boolean value that is synchronized with the underlying collection or tableView cell property. - * Setting this value is equivalent to calling highlightItem / unHighlightItem on the collection or table. - */ -@property (getter=isHighlighted) BOOL highlighted; - -/** - * The current index path of this cell node, or @c nil if this node is - * not a valid item inside a table node or collection node. - */ -@property (nullable, copy, readonly) NSIndexPath *indexPath; - -/** - * BETA: API is under development. We will attempt to provide an easy migration pathway for any changes. - * - * The view-model currently assigned to this node, if any. - * - * This property may be set off the main thread, but this method will never be invoked concurrently on the - */ -@property (nullable) id nodeModel; - -/** - * Asks the node whether it can be updated to the given node model. - * - * The default implementation returns YES if the class matches that of the current view-model. - */ -- (BOOL)canUpdateToNodeModel:(id)nodeModel; - -/** - * The backing view controller, or @c nil if the node wasn't initialized with backing view controller - * @note This property must be accessed on the main thread. - */ -@property (nullable, nonatomic, readonly) UIViewController *viewController; - - -/** - * The table- or collection-node that this cell is a member of, if any. - */ -@property (nullable, weak, readonly) id owningNode; - -/* - * ASCellNode must forward touch events in order for UITableView and UICollectionView tap handling to work. Overriding - * these methods (e.g. for highlighting) requires the super method be called. - */ -- (void)touchesBegan:(NSSet *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER; -- (void)touchesMoved:(NSSet *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER; -- (void)touchesEnded:(NSSet *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER; -- (void)touchesCancelled:(nullable NSSet *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER; - -/** - * Called by the system when ASCellNode is used with an ASCollectionNode. It will not be called by ASTableNode. - * When the UICollectionViewLayout object returns a new UICollectionViewLayoutAttributes object, the corresponding ASCellNode will be updated. - * See UICollectionViewCell's applyLayoutAttributes: for a full description. -*/ -- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes; - -/** - * @abstract Initializes a cell with a given view controller block. - * - * @param viewControllerBlock The block that will be used to create the backing view controller. - * @param didLoadBlock The block that will be called after the view controller's view is loaded. - * - * @return An ASCellNode created using the root view of the view controller provided by the viewControllerBlock. - * The view controller's root view is resized to match the calculated size produced during layout. - * - */ -- (instancetype)initWithViewControllerBlock:(ASDisplayNodeViewControllerBlock)viewControllerBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock; - -/** - * @abstract Notifies the cell node of certain visibility events, such as changing visible rect. - * - * @warning In cases where an ASCellNode is used as a plain node – i.e. not returned from the - * nodeBlockForItemAtIndexPath/nodeForItemAtIndexPath data source methods – this method will - * deliver only the `Visible` and `Invisible` events, `scrollView` will be nil, and - * `cellFrame` will be the zero rect. - */ -- (void)cellNodeVisibilityEvent:(ASCellNodeVisibilityEvent)event inScrollView:(nullable UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame; - -#pragma mark - UITableViewCell specific passthrough properties - -/* @abstract The selection style when a tap on a cell occurs - * @default UITableViewCellSelectionStyleDefault - * ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes. - */ -@property UITableViewCellSelectionStyle selectionStyle; - -/* @abstract The focus style when a cell is focused - * @default UITableViewCellFocusStyleDefault - * ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes. - */ -@property UITableViewCellFocusStyle focusStyle; - -/* @abstract The view used as the background of the cell when it is selected. - * ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes. - * ASCollectionView uses these properties when configuring UICollectionViewCells that host ASCellNodes. - */ -@property (nullable) UIView *selectedBackgroundView; - -/* @abstract The view used as the background of the cell. - * ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes. - * ASCollectionView uses these properties when configuring UICollectionViewCells that host ASCellNodes. - */ -@property (nullable) UIView *backgroundView; - -/* @abstract The accessory type view on the right side of the cell. Please take care of your ASLayoutSpec so that doesn't overlay the accessoryView - * @default UITableViewCellAccessoryNone - * ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes. - */ -@property UITableViewCellAccessoryType accessoryType; - -/* @abstract The inset of the cell separator line - * ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes. - */ -@property UIEdgeInsets separatorInset; - -@end - -@interface ASCellNode (Unavailable) - -- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_UNAVAILABLE; - -- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_UNAVAILABLE; - -- (void)setLayerBacked:(BOOL)layerBacked AS_UNAVAILABLE("ASCellNode does not support layer-backing, although subnodes may be layer-backed."); - -@end - - -/** - * Simple label-style cell node. Read its source for an example of custom s. - */ -@interface ASTextCellNode : ASCellNode - -/** - * Initializes a text cell with given text attributes and text insets - */ -- (instancetype)initWithAttributes:(NSDictionary *)textAttributes insets:(UIEdgeInsets)textInsets; - -/** - * Text to display. - */ -@property (nullable, copy) NSString *text; - -/** - * A dictionary containing key-value pairs for text attributes. You can specify the font, text color, text shadow color, and text shadow offset using the keys listed in NSString UIKit Additions Reference. - */ -@property (copy) NSDictionary *textAttributes; - -/** - * The text inset or outset for each edge. The default value is 15.0 horizontal and 11.0 vertical padding. - */ -@property UIEdgeInsets textInsets; - -/** - * The text node used by this cell node. - */ -@property (readonly) ASTextNode *textNode; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCellNode.mm b/submodules/AsyncDisplayKit/Source/ASCellNode.mm deleted file mode 100644 index 5793dfabd7..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCellNode.mm +++ /dev/null @@ -1,488 +0,0 @@ -// -// ASCellNode.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#import -#import -#import - -#pragma mark - -#pragma mark ASCellNode - -@interface ASCellNode () -{ - ASDisplayNodeViewControllerBlock _viewControllerBlock; - ASDisplayNodeDidLoadBlock _viewControllerDidLoadBlock; - ASDisplayNode *_viewControllerNode; - UIViewController *_viewController; - BOOL _suspendInteractionDelegate; - BOOL _selected; - BOOL _highlighted; - UICollectionViewLayoutAttributes *_layoutAttributes; -} - -@end - -@implementation ASCellNode -@synthesize interactionDelegate = _interactionDelegate; - -- (instancetype)init -{ - if (!(self = [super init])) - return nil; - - // Use UITableViewCell defaults - _selectionStyle = UITableViewCellSelectionStyleDefault; - _focusStyle = UITableViewCellFocusStyleDefault; - self.clipsToBounds = YES; - - return self; -} - -- (instancetype)initWithViewControllerBlock:(ASDisplayNodeViewControllerBlock)viewControllerBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock -{ - if (!(self = [super init])) - return nil; - - ASDisplayNodeAssertNotNil(viewControllerBlock, @"should initialize with a valid block that returns a UIViewController"); - _viewControllerBlock = viewControllerBlock; - _viewControllerDidLoadBlock = didLoadBlock; - - return self; -} - -- (void)didLoad -{ - [super didLoad]; - - if (_viewControllerBlock != nil) { - - _viewController = _viewControllerBlock(); - _viewControllerBlock = nil; - - if ([_viewController isKindOfClass:[ASViewController class]]) { - ASViewController *asViewController = (ASViewController *)_viewController; - _viewControllerNode = asViewController.node; - [_viewController loadViewIfNeeded]; - } else { - // Careful to avoid retain cycle - UIViewController *viewController = _viewController; - _viewControllerNode = [[ASDisplayNode alloc] initWithViewBlock:^{ - return viewController.view; - }]; - } - [self addSubnode:_viewControllerNode]; - - // Since we just loaded our node, and added _viewControllerNode as a subnode, - // _viewControllerNode must have just loaded its view, so now is an appropriate - // time to execute our didLoadBlock, if we were given one. - if (_viewControllerDidLoadBlock != nil) { - _viewControllerDidLoadBlock(self); - _viewControllerDidLoadBlock = nil; - } - } -} - -- (void)layout -{ - [super layout]; - - _viewControllerNode.frame = self.bounds; -} - -- (void)_rootNodeDidInvalidateSize -{ - if (_interactionDelegate != nil) { - [_interactionDelegate nodeDidInvalidateSize:self]; - } else { - [super _rootNodeDidInvalidateSize]; - } -} - -- (void)_layoutTransitionMeasurementDidFinish -{ - if (_interactionDelegate != nil) { - [_interactionDelegate nodeDidInvalidateSize:self]; - } else { - [super _layoutTransitionMeasurementDidFinish]; - } -} - -- (BOOL)isSelected -{ - return ASLockedSelf(_selected); -} - -- (void)setSelected:(BOOL)selected -{ - if (ASLockedSelfCompareAssign(_selected, selected)) { - if (!_suspendInteractionDelegate) { - ASPerformBlockOnMainThread(^{ - [_interactionDelegate nodeSelectedStateDidChange:self]; - }); - } - } -} - -- (BOOL)isHighlighted -{ - return ASLockedSelf(_highlighted); -} - -- (void)setHighlighted:(BOOL)highlighted -{ - if (ASLockedSelfCompareAssign(_highlighted, highlighted)) { - if (!_suspendInteractionDelegate) { - ASPerformBlockOnMainThread(^{ - [_interactionDelegate nodeHighlightedStateDidChange:self]; - }); - } - } -} - -- (void)__setSelectedFromUIKit:(BOOL)selected; -{ - // Note: Race condition could mean redundant sets. Risk is low. - if (ASLockedSelf(_selected != selected)) { - _suspendInteractionDelegate = YES; - self.selected = selected; - _suspendInteractionDelegate = NO; - } -} - -- (void)__setHighlightedFromUIKit:(BOOL)highlighted; -{ - // Note: Race condition could mean redundant sets. Risk is low. - if (ASLockedSelf(_highlighted != highlighted)) { - _suspendInteractionDelegate = YES; - self.highlighted = highlighted; - _suspendInteractionDelegate = NO; - } -} - -- (BOOL)canUpdateToNodeModel:(id)nodeModel -{ - return [self.nodeModel class] == [nodeModel class]; -} - -- (NSIndexPath *)indexPath -{ - return [self.owningNode indexPathForNode:self]; -} - -- (UIViewController *)viewController -{ - ASDisplayNodeAssertMainThread(); - // Force the view to load so that we will create the - // view controller if we haven't already. - if (self.isNodeLoaded == NO) { - [self view]; - } - return _viewController; -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wobjc-missing-super-calls" - -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert([self.view isKindOfClass:_ASDisplayView.class], @"ASCellNode views must be of type _ASDisplayView"); - [(_ASDisplayView *)self.view __forwardTouchesBegan:touches withEvent:event]; -} - -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert([self.view isKindOfClass:_ASDisplayView.class], @"ASCellNode views must be of type _ASDisplayView"); - [(_ASDisplayView *)self.view __forwardTouchesMoved:touches withEvent:event]; -} - -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert([self.view isKindOfClass:_ASDisplayView.class], @"ASCellNode views must be of type _ASDisplayView"); - [(_ASDisplayView *)self.view __forwardTouchesEnded:touches withEvent:event]; -} - -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert([self.view isKindOfClass:_ASDisplayView.class], @"ASCellNode views must be of type _ASDisplayView"); - [(_ASDisplayView *)self.view __forwardTouchesCancelled:touches withEvent:event]; -} - -#pragma clang diagnostic pop - -- (UICollectionViewLayoutAttributes *)layoutAttributes -{ - return ASLockedSelf(_layoutAttributes); -} - -- (void)setLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes -{ - ASDisplayNodeAssertMainThread(); - if (ASLockedSelfCompareAssignObjects(_layoutAttributes, layoutAttributes)) { - if (layoutAttributes != nil) { - [self applyLayoutAttributes:layoutAttributes]; - } - } -} - -- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes -{ - // To be overriden by subclasses -} - -- (void)cellNodeVisibilityEvent:(ASCellNodeVisibilityEvent)event inScrollView:(UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame -{ - // To be overriden by subclasses -} - -- (void)didEnterVisibleState -{ - [super didEnterVisibleState]; - if (self.neverShowPlaceholders) { - [self recursivelyEnsureDisplaySynchronously:YES]; - } - [self handleVisibilityChange:YES]; -} - -- (void)didExitVisibleState -{ - [super didExitVisibleState]; - [self handleVisibilityChange:NO]; -} - -+ (BOOL)requestsVisibilityNotifications -{ - static NSCache *cache; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - cache = [[NSCache alloc] init]; - }); - NSNumber *result = [cache objectForKey:self]; - if (result == nil) { - BOOL overrides = ASSubclassOverridesSelector([ASCellNode class], self, @selector(cellNodeVisibilityEvent:inScrollView:withCellFrame:)); - result = overrides ? (NSNumber *)kCFBooleanTrue : (NSNumber *)kCFBooleanFalse; - [cache setObject:result forKey:self]; - } - return (result == (NSNumber *)kCFBooleanTrue); -} - -- (void)handleVisibilityChange:(BOOL)isVisible -{ - if ([self.class requestsVisibilityNotifications] == NO) { - return; // The work below is expensive, and only valuable for subclasses watching visibility events. - } - - // NOTE: This assertion is failing in some apps and will be enabled soon. - // ASDisplayNodeAssert(self.isNodeLoaded, @"Node should be loaded in order for it to become visible or invisible. If not in this situation, we shouldn't trigger creating the view."); - - UIView *view = self.view; - CGRect cellFrame = CGRectZero; - - // Ensure our _scrollView is still valid before converting. It's also possible that we have already been removed from the _scrollView, - // in which case it is not valid to perform a convertRect (this actually crashes on iOS 8). - UIScrollView *scrollView = (_scrollView != nil && view.superview != nil && [view isDescendantOfView:_scrollView]) ? _scrollView : nil; - if (scrollView) { - cellFrame = [view convertRect:view.bounds toView:_scrollView]; - } - - // If we did not convert, we'll pass along CGRectZero and a nil scrollView. The EventInvisible call is thus equivalent to - // didExitVisibileState, but is more convenient for the developer than implementing multiple methods. - [self cellNodeVisibilityEvent:isVisible ? ASCellNodeVisibilityEventVisible - : ASCellNodeVisibilityEventInvisible - inScrollView:scrollView - withCellFrame:cellFrame]; -} - -- (NSMutableArray *)propertiesForDebugDescription -{ - NSMutableArray *result = [super propertiesForDebugDescription]; - - UIScrollView *scrollView = self.scrollView; - - ASDisplayNode *owningNode = scrollView.asyncdisplaykit_node; - if ([owningNode isKindOfClass:[ASCollectionNode class]]) { - NSIndexPath *ip = [(ASCollectionNode *)owningNode indexPathForNode:self]; - if (ip != nil) { - [result addObject:@{ @"indexPath" : ip }]; - } - [result addObject:@{ @"collectionNode" : owningNode }]; - } else if ([owningNode isKindOfClass:[ASTableNode class]]) { - NSIndexPath *ip = [(ASTableNode *)owningNode indexPathForNode:self]; - if (ip != nil) { - [result addObject:@{ @"indexPath" : ip }]; - } - [result addObject:@{ @"tableNode" : owningNode }]; - - } else if ([scrollView isKindOfClass:[ASCollectionView class]]) { - NSIndexPath *ip = [(ASCollectionView *)scrollView indexPathForNode:self]; - if (ip != nil) { - [result addObject:@{ @"indexPath" : ip }]; - } - [result addObject:@{ @"collectionView" : ASObjectDescriptionMakeTiny(scrollView) }]; - - } else if ([scrollView isKindOfClass:[ASTableView class]]) { - NSIndexPath *ip = [(ASTableView *)scrollView indexPathForNode:self]; - if (ip != nil) { - [result addObject:@{ @"indexPath" : ip }]; - } - [result addObject:@{ @"tableView" : ASObjectDescriptionMakeTiny(scrollView) }]; - } - - return result; -} - -- (NSString *)supplementaryElementKind -{ - return self.collectionElement.supplementaryElementKind; -} - -- (BOOL)supportsLayerBacking -{ - return NO; -} - -- (BOOL)shouldUseUIKitCell -{ - return NO; -} - -@end - - -#pragma mark - -#pragma mark ASWrapperCellNode - -// TODO: Consider if other calls, such as willDisplayCell, should be bridged to this class. -@implementation ASWrapperCellNode : ASCellNode - -- (BOOL)shouldUseUIKitCell -{ - return YES; -} - -@end - - -#pragma mark - -#pragma mark ASTextCellNode - -@implementation ASTextCellNode { - NSDictionary *_textAttributes; - UIEdgeInsets _textInsets; - NSString *_text; -} - -static const CGFloat kASTextCellNodeDefaultFontSize = 18.0f; -static const CGFloat kASTextCellNodeDefaultHorizontalPadding = 15.0f; -static const CGFloat kASTextCellNodeDefaultVerticalPadding = 11.0f; - -- (instancetype)init -{ - return [self initWithAttributes:[ASTextCellNode defaultTextAttributes] insets:[ASTextCellNode defaultTextInsets]]; -} - -- (instancetype)initWithAttributes:(NSDictionary *)textAttributes insets:(UIEdgeInsets)textInsets -{ - self = [super init]; - if (self) { - _textInsets = textInsets; - _textAttributes = [textAttributes copy]; - _textNode = [[ASTextNode alloc] init]; - self.automaticallyManagesSubnodes = YES; - } - return self; -} - -- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize -{ - return [ASInsetLayoutSpec insetLayoutSpecWithInsets:self.textInsets child:self.textNode]; -} - -+ (NSDictionary *)defaultTextAttributes -{ - return @{NSFontAttributeName : [UIFont systemFontOfSize:kASTextCellNodeDefaultFontSize]}; -} - -+ (UIEdgeInsets)defaultTextInsets -{ - return UIEdgeInsetsMake(kASTextCellNodeDefaultVerticalPadding, kASTextCellNodeDefaultHorizontalPadding, kASTextCellNodeDefaultVerticalPadding, kASTextCellNodeDefaultHorizontalPadding); -} - -- (NSDictionary *)textAttributes -{ - return ASLockedSelf(_textAttributes); -} - -- (void)setTextAttributes:(NSDictionary *)textAttributes -{ - ASDisplayNodeAssertNotNil(textAttributes, @"Invalid text attributes"); - ASLockScopeSelf(); - if (ASCompareAssignCopy(_textAttributes, textAttributes)) { - [self locked_updateAttributedText]; - } -} - -- (UIEdgeInsets)textInsets -{ - return ASLockedSelf(_textInsets); -} - -- (void)setTextInsets:(UIEdgeInsets)textInsets -{ - if (ASLockedSelfCompareAssignCustom(_textInsets, textInsets, UIEdgeInsetsEqualToEdgeInsets)) { - [self setNeedsLayout]; - } -} - -- (NSString *)text -{ - return ASLockedSelf(_text); -} - -- (void)setText:(NSString *)text -{ - ASLockScopeSelf(); - if (ASCompareAssignCopy(_text, text)) { - [self locked_updateAttributedText]; - } -} - -- (void)locked_updateAttributedText -{ - if (_text == nil) { - _textNode.attributedText = nil; - return; - } - - _textNode.attributedText = [[NSAttributedString alloc] initWithString:_text attributes:_textAttributes]; - [self setNeedsLayout]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionNode+Beta.h b/submodules/AsyncDisplayKit/Source/ASCollectionNode+Beta.h deleted file mode 100644 index 5aefbb8a76..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionNode+Beta.h +++ /dev/null @@ -1,82 +0,0 @@ -// -// ASCollectionNode+Beta.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK -#import - -@protocol ASCollectionViewLayoutFacilitatorProtocol, ASCollectionLayoutDelegate, ASBatchFetchingDelegate; -@class ASElementMap; - -NS_ASSUME_NONNULL_BEGIN - -@interface ASCollectionNode (Beta) - -/** - * Allows providing a custom subclass of ASCollectionView to be managed by ASCollectionNode. - * - * @default [ASCollectionView class] is used whenever this property is unset or nil. - */ -@property (nullable, nonatomic) Class collectionViewClass; - -/** - * The elements that are currently displayed. The "UIKit index space". Must be accessed on main thread. - */ -@property (nonatomic, readonly) ASElementMap *visibleElements; - -@property (nullable, readonly) id layoutDelegate; - -@property (nullable, nonatomic, weak) id batchFetchingDelegate; - -/** - * When this mode is enabled, ASCollectionView matches the timing of UICollectionView as closely as - * possible, ensuring that all reload and edit operations are performed on the main thread as - * blocking calls. - * - * This mode is useful for applications that are debugging issues with their collection view - * implementation. In particular, some applications do not properly conform to the API requirement - * of UICollectionView, and these applications may experience difficulties with ASCollectionView. - * Providing this mode allows for developers to work towards resolving technical debt in their - * collection view data source, while ramping up asynchronous collection layout. - * - * NOTE: Because this mode results in expensive operations like cell layout being performed on the - * main thread, it should be used as a tool to resolve data source conformance issues with Apple - * collection view API. - * - * @default defaults to ASCellLayoutModeNone. - */ -@property (nonatomic) ASCellLayoutMode cellLayoutMode; - -/** - * Returns YES if the ASCollectionNode contents are completely synchronized with the underlying collection-view layout. - */ -@property (nonatomic, readonly, getter=isSynchronized) BOOL synchronized; - -/** - * Schedules a block to be performed (on the main thread) as soon as the completion block is called - * on performBatchUpdates:. - * - * When isSynchronized == YES, the block is run block immediately (before the method returns). - */ -- (void)onDidFinishSynchronizing:(void (^)(void))didFinishSynchronizing; - -- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(nullable id)layoutFacilitator; - -- (instancetype)initWithLayoutDelegate:(id)layoutDelegate layoutFacilitator:(nullable id)layoutFacilitator; - -- (void)beginUpdates ASDISPLAYNODE_DEPRECATED_MSG("Use -performBatchUpdates:completion: instead."); - -- (void)endUpdatesAnimated:(BOOL)animated ASDISPLAYNODE_DEPRECATED_MSG("Use -performBatchUpdates:completion: instead."); - -- (void)endUpdatesAnimated:(BOOL)animated completion:(nullable void (^)(BOOL))completion ASDISPLAYNODE_DEPRECATED_MSG("Use -performBatchUpdates:completion: instead."); - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionNode.h b/submodules/AsyncDisplayKit/Source/ASCollectionNode.h deleted file mode 100644 index bbcd8befef..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionNode.h +++ /dev/null @@ -1,954 +0,0 @@ -// -// ASCollectionNode.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import -#import -#import -#import -#import - -@protocol ASCollectionViewLayoutFacilitatorProtocol; -@protocol ASCollectionDelegate; -@protocol ASCollectionDataSource; -@class ASCollectionView; - -NS_ASSUME_NONNULL_BEGIN - -/** - * ASCollectionNode is a node based class that wraps an ASCollectionView. It can be used - * as a subnode of another node, and provide room for many (great) features and improvements later on. - */ -@interface ASCollectionNode : ASDisplayNode - -- (instancetype)init NS_UNAVAILABLE; - -/** - * Initializes an ASCollectionNode - * - * @discussion Initializes and returns a newly allocated collection node object with the specified layout. - * - * @param layout The layout object to use for organizing items. The collection view stores a strong reference to the specified object. Must not be nil. - */ -- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout; - -/** - * Initializes an ASCollectionNode - * - * @discussion Initializes and returns a newly allocated collection node object with the specified frame and layout. - * - * @param frame The frame rectangle for the collection view, measured in points. The origin of the frame is relative to the superview in which you plan to add it. This frame is passed to the superclass during initialization. - * @param layout The layout object to use for organizing items. The collection view stores a strong reference to the specified object. Must not be nil. - */ -- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout; - -/** - * Returns the corresponding ASCollectionView - * - * @return view The corresponding ASCollectionView. - */ -@property (readonly) ASCollectionView *view; - -/** - * The object that acts as the asynchronous delegate of the collection view - * - * @discussion The delegate must adopt the ASCollectionDelegate protocol. The collection view maintains a weak reference to the delegate object. - * - * The delegate object is responsible for providing size constraints for nodes and indicating whether batch fetching should begin. - * @note This is a convenience method which sets the asyncDelegate on the collection node's collection view. - */ -@property (nullable, weak) id delegate; - -/** - * The object that acts as the asynchronous data source of the collection view - * - * @discussion The datasource must adopt the ASCollectionDataSource protocol. The collection view maintains a weak reference to the datasource object. - * - * The datasource object is responsible for providing nodes or node creation blocks to the collection view. - * @note This is a convenience method which sets the asyncDatasource on the collection node's collection view. - */ -@property (nullable, weak) id dataSource; - -/** - * The number of screens left to scroll before the delegate -collectionNode:beginBatchFetchingWithContext: is called. - * - * Defaults to two screenfuls. - */ -@property (nonatomic) CGFloat leadingScreensForBatching; - -/* - * A Boolean value that determines whether the collection node will be flipped. - * If the value of this property is YES, the first cell node will be at the bottom of the collection node (as opposed to the top by default). This is useful for chat/messaging apps. The default value is NO. - */ -@property (nonatomic) BOOL inverted; - -/** - * A Boolean value that indicates whether users can select items in the collection node. - * If the value of this property is YES (the default), users can select items. If you want more fine-grained control over the selection of items, you must provide a delegate object and implement the appropriate methods of the UICollectionNodeDelegate protocol. - */ -@property (nonatomic) BOOL allowsSelection; - -/** - * A Boolean value that determines whether users can select more than one item in the collection node. - * This property controls whether multiple items can be selected simultaneously. The default value of this property is NO. - * When the value of this property is YES, tapping a cell adds it to the current selection (assuming the delegate permits the cell to be selected). Tapping the cell again removes it from the selection. - */ -@property (nonatomic) BOOL allowsMultipleSelection; - -/** - * A Boolean value that determines whether bouncing always occurs when vertical scrolling reaches the end of the content. - * The default value of this property is NO. - */ -@property (nonatomic) BOOL alwaysBounceVertical; - -/** - * A Boolean value that determines whether bouncing always occurs when horizontal scrolling reaches the end of the content view. - * The default value of this property is NO. - */ -@property (nonatomic) BOOL alwaysBounceHorizontal; - -/** - * A Boolean value that controls whether the vertical scroll indicator is visible. - * The default value of this property is YES. - */ -@property (nonatomic) BOOL showsVerticalScrollIndicator; - -/** - * A Boolean value that controls whether the horizontal scroll indicator is visible. - * The default value of this property is NO. - */ -@property (nonatomic) BOOL showsHorizontalScrollIndicator; - -/** - * The layout used to organize the node's items. - * - * @discussion Assigning a new layout object to this property causes the new layout to be applied (without animations) to the node’s items. - */ -@property (nonatomic) UICollectionViewLayout *collectionViewLayout; - -/** - * Optional introspection object for the collection node's layout. - * - * @discussion Since supplementary and decoration nodes are controlled by the layout, this object - * is used as a bridge to provide information to the internal data controller about the existence of these views and - * their associated index paths. For collections using `UICollectionViewFlowLayout`, a default inspector - * implementation `ASCollectionViewFlowLayoutInspector` is created and set on this property by default. Custom - * collection layout subclasses will need to provide their own implementation of an inspector object for their - * supplementary elements to be compatible with `ASCollectionNode`'s supplementary node support. - */ -@property (nonatomic, weak) id layoutInspector; - -/** - * The distance that the content view is inset from the collection node edges. Defaults to UIEdgeInsetsZero. - */ -@property (nonatomic) UIEdgeInsets contentInset; - -/** - * The offset of the content view's origin from the collection node's origin. Defaults to CGPointZero. - */ -@property (nonatomic) CGPoint contentOffset; - -/** - * Sets the offset from the content node’s origin to the collection node’s origin. - * - * @param contentOffset The offset - * - * @param animated YES to animate to this new offset at a constant velocity, NO to not aniamte and immediately make the transition. - */ -- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated; - -/** - * Tuning parameters for a range type in full mode. - * - * @param rangeType The range type to get the tuning parameters for. - * - * @return A tuning parameter value for the given range type in full mode. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType AS_WARN_UNUSED_RESULT; - -/** - * Set the tuning parameters for a range type in full mode. - * - * @param tuningParameters The tuning parameters to store for a range type. - * @param rangeType The range type to set the tuning parameters for. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType; - -/** - * Tuning parameters for a range type in the specified mode. - * - * @param rangeMode The range mode to get the running parameters for. - * @param rangeType The range type to get the tuning parameters for. - * - * @return A tuning parameter value for the given range type in the given mode. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType AS_WARN_UNUSED_RESULT; - -/** - * Set the tuning parameters for a range type in the specified mode. - * - * @param tuningParameters The tuning parameters to store for a range type. - * @param rangeMode The range mode to set the running parameters for. - * @param rangeType The range type to set the tuning parameters for. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; - -/** - * Scrolls the collection to the given item. - * - * @param indexPath The index path of the item. - * @param scrollPosition Where the item should end up after the scroll. - * @param animated Whether the scroll should be animated or not. - * - * This method must be called on the main thread. - */ -- (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated; - -/** - * Determines collection node's current scroll direction. Supports 2-axis collection nodes. - * - * @return a bitmask of ASScrollDirection values. - */ -@property (nonatomic, readonly) ASScrollDirection scrollDirection; - -/** - * Determines collection node's scrollable directions. - * - * @return a bitmask of ASScrollDirection values. - */ -@property (nonatomic, readonly) ASScrollDirection scrollableDirections; - -#pragma mark - Editing - -/** - * Registers the given kind of supplementary node for use in creating node-backed supplementary elements. - * - * @param elementKind The kind of supplementary node that will be requested through the data source. - * - * @discussion Use this method to register support for the use of supplementary nodes in place of the default - * `registerClass:forSupplementaryViewOfKind:withReuseIdentifier:` and `registerNib:forSupplementaryViewOfKind:withReuseIdentifier:` - * methods. This method will register an internal backing view that will host the contents of the supplementary nodes - * returned from the data source. - */ -- (void)registerSupplementaryNodeOfKind:(NSString *)elementKind; - -/** - * Perform a batch of updates asynchronously, optionally disabling all animations in the batch. This method must be called from the main thread. - * The data source must be updated to reflect the changes before the update block completes. - * - * @param animated NO to disable animations for this batch - * @param updates The block that performs the relevant insert, delete, reload, or move operations. - * @param completion A completion handler block to execute when all of the operations are finished. This block takes a single - * Boolean parameter that contains the value YES if all of the related animations completed successfully or - * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. - */ -- (void)performBatchAnimated:(BOOL)animated updates:(nullable AS_NOESCAPE void (^)(void))updates completion:(nullable void (^)(BOOL finished))completion; - -/** - * Perform a batch of updates asynchronously, optionally disabling all animations in the batch. This method must be called from the main thread. - * The data source must be updated to reflect the changes before the update block completes. - * - * @param updates The block that performs the relevant insert, delete, reload, or move operations. - * @param completion A completion handler block to execute when all of the operations are finished. This block takes a single - * Boolean parameter that contains the value YES if all of the related animations completed successfully or - * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. - */ -- (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)(void))updates completion:(nullable void (^)(BOOL finished))completion; - -/** - * Returns YES if the ASCollectionNode is still processing changes from performBatchUpdates:. - * This is typically the concurrent allocation (calling nodeBlocks) and layout of newly inserted - * ASCellNodes. If YES is returned, then calling -waitUntilAllUpdatesAreProcessed may take tens of - * milliseconds to return as it blocks on these concurrent operations. - * - * Returns NO if ASCollectionNode is fully synchronized with the underlying UICollectionView. This - * means that until the next performBatchUpdates: is called, it is safe to compare UIKit values - * (such as from UICollectionViewLayout) with your app's data source. - * - * This method will always return NO if called immediately after -waitUntilAllUpdatesAreProcessed. - */ -@property (nonatomic, readonly) BOOL isProcessingUpdates; - -/** - * Schedules a block to be performed (on the main thread) after processing of performBatchUpdates: - * is finished (completely synchronized to UIKit). The blocks will be run at the moment that - * -isProcessingUpdates changes from YES to NO; - * - * When isProcessingUpdates == NO, the block is run block immediately (before the method returns). - * - * Blocks scheduled by this mechanism are NOT guaranteed to run in the order they are scheduled. - * They may also be delayed if performBatchUpdates continues to be called; the blocks will wait until - * all running updates are finished. - * - * Calling -waitUntilAllUpdatesAreProcessed is one way to flush any pending update completion blocks. - */ -- (void)onDidFinishProcessingUpdates:(void (^)(void))didFinishProcessingUpdates; - -/** - * Blocks execution of the main thread until all section and item updates are committed to the view. This method must be called from the main thread. - */ -- (void)waitUntilAllUpdatesAreProcessed; - -/** - * Inserts one or more sections. - * - * @param sections An index set that specifies the sections to insert. - * - * @discussion This method must be called from the main thread. The data source must be updated to reflect the changes - * before this method is called. - */ -- (void)insertSections:(NSIndexSet *)sections; - -/** - * Deletes one or more sections. - * - * @param sections An index set that specifies the sections to delete. - * - * @discussion This method must be called from the main thread. The data source must be updated to reflect the changes - * before this method is called. - */ -- (void)deleteSections:(NSIndexSet *)sections; - -/** - * Reloads the specified sections. - * - * @param sections An index set that specifies the sections to reload. - * - * @discussion This method must be called from the main thread. The data source must be updated to reflect the changes - * before this method is called. - */ -- (void)reloadSections:(NSIndexSet *)sections; - -/** - * Moves a section to a new location. - * - * @param section The index of the section to move. - * - * @param newSection The index that is the destination of the move for the section. - * - * @discussion This method must be called from the main thread. The data source must be updated to reflect the changes - * before this method is called. - */ -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection; - -/** - * Inserts items at the locations identified by an array of index paths. - * - * @param indexPaths An array of NSIndexPath objects, each representing an item index and section index that together identify an item. - * - * @discussion This method must be called from the main thread. The data source must be updated to reflect the changes - * before this method is called. - */ -- (void)insertItemsAtIndexPaths:(NSArray *)indexPaths; - -/** - * Deletes the items specified by an array of index paths. - * - * @param indexPaths An array of NSIndexPath objects identifying the items to delete. - * - * @discussion This method must be called from the main thread. The data source must be updated to reflect the changes - * before this method is called. - */ -- (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths; - -/** - * Reloads the specified items. - * - * @param indexPaths An array of NSIndexPath objects identifying the items to reload. - * - * @discussion This method must be called from the main thread. The data source must be updated to reflect the changes - * before this method is called. - */ -- (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths; - -/** - * Moves the item at a specified location to a destination location. - * - * @param indexPath The index path identifying the item to move. - * - * @param newIndexPath The index path that is the destination of the move for the item. - * - * @discussion This method must be called from the main thread. The data source must be updated to reflect the changes - * before this method is called. - */ -- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath; - -/** - * Reload everything from scratch, destroying the working range and all cached nodes. - * - * @param completion block to run on completion of asynchronous loading or nil. If supplied, the block is run on - * the main thread. - * @warning This method is substantially more expensive than UICollectionView's version. - */ -- (void)reloadDataWithCompletion:(nullable void (^)(void))completion; - - -/** - * Reload everything from scratch, destroying the working range and all cached nodes. - * - * @warning This method is substantially more expensive than UICollectionView's version. - */ -- (void)reloadData; - -/** - * Triggers a relayout of all nodes. - * - * @discussion This method invalidates and lays out every cell node in the collection view. - */ -- (void)relayoutItems; - -#pragma mark - Selection - -/** - * The index paths of the selected items, or @c nil if no items are selected. - */ -@property (nullable, nonatomic, copy, readonly) NSArray *indexPathsForSelectedItems; - -/** - * Selects the item at the specified index path and optionally scrolls it into view. - * If the `allowsSelection` property is NO, calling this method has no effect. If there is an existing selection with a different index path and the `allowsMultipleSelection` property is NO, calling this method replaces the previous selection. - * This method does not cause any selection-related delegate methods to be called. - * - * @param indexPath The index path of the item to select. Specifying nil for this parameter clears the current selection. - * - * @param animated Specify YES to animate the change in the selection or NO to make the change without animating it. - * - * @param scrollPosition An option that specifies where the item should be positioned when scrolling finishes. For a list of possible values, see `UICollectionViewScrollPosition`. - * - * @discussion This method must be called from the main thread. - */ -- (void)selectItemAtIndexPath:(nullable NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UICollectionViewScrollPosition)scrollPosition; - -/** - * Deselects the item at the specified index. - * If the allowsSelection property is NO, calling this method has no effect. - * This method does not cause any selection-related delegate methods to be called. - * - * @param indexPath The index path of the item to select. Specifying nil for this parameter clears the current selection. - * - * @param animated Specify YES to animate the change in the selection or NO to make the change without animating it. - * - * @discussion This method must be called from the main thread. - */ -- (void)deselectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated; - -#pragma mark - Querying Data - -/** - * Retrieves the number of items in the given section. - * - * @param section The section. - * - * @return The number of items. - */ -- (NSInteger)numberOfItemsInSection:(NSInteger)section AS_WARN_UNUSED_RESULT; - -/** - * The number of sections. - */ -@property (nonatomic, readonly) NSInteger numberOfSections; - -/** - * Similar to -visibleCells. - * - * @return an array containing the nodes being displayed on screen. This must be called on the main thread. - */ -@property (nonatomic, readonly) NSArray<__kindof ASCellNode *> *visibleNodes; - -/** - * Retrieves the node for the item at the given index path. - * - * @param indexPath The index path of the requested item. - * - * @return The node for the given item, or @c nil if no item exists at the specified path. - */ -- (nullable __kindof ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT; - -/** - * Retrieves the node-model for the item at the given index path, if any. - * - * @param indexPath The index path of the requested item. - * - * @return The node-model for the given item, or @c nil if no item exists at the specified path or no node-model was provided. - * - * @warning This API is beta and subject to change. We'll try to provide an easy migration path. - */ -- (nullable id)nodeModelForItemAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT; - -/** - * Retrieve the index path for the item with the given node. - * - * @param cellNode A node for an item in the collection node. - * - * @return The indexPath for this item. - */ -- (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode AS_WARN_UNUSED_RESULT; - -/** - * Retrieve the index paths of all visible items. - * - * @return an array containing the index paths of all visible items. This must be called on the main thread. - */ -@property (nonatomic, readonly) NSArray *indexPathsForVisibleItems; - -/** - * Retrieve the index path of the item at the given point. - * - * @param point The point of the requested item. - * - * @return The indexPath for the item at the given point. This must be called on the main thread. - */ -- (nullable NSIndexPath *)indexPathForItemAtPoint:(CGPoint)point AS_WARN_UNUSED_RESULT; - -/** - * Retrieve the cell at the given index path. - * - * @param indexPath The index path of the requested item. - * - * @return The cell for the given index path. This must be called on the main thread. - */ -- (nullable UICollectionViewCell *)cellForItemAtIndexPath:(NSIndexPath *)indexPath; - -/** - * Retrieves the context object for the given section, as provided by the data source in - * the @c collectionNode:contextForSection: method. - * - * @param section The section to get the context for. - * - * @return The context object, or @c nil if no context was provided. - * - * TODO: This method currently accepts @c section in the _view_ index space, but it should - * be in the node index space. To get the context in the view index space (e.g. for subclasses - * of @c UICollectionViewLayout, the user will call the same method on @c ASCollectionView. - */ -- (nullable id)contextForSection:(NSInteger)section AS_WARN_UNUSED_RESULT; - -@end - -@interface ASCollectionNode (Deprecated) - -- (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("This method has been renamed to -waitUntilAllUpdatesAreProcessed."); - -@end - -/** - * This is a node-based UICollectionViewDataSource. - */ -@protocol ASCollectionDataSource - -@optional - -/** - * Asks the data source for the number of items in the given section of the collection node. - * - * @see @c collectionView:numberOfItemsInSection: - */ -- (NSInteger)collectionNode:(ASCollectionNode *)collectionNode numberOfItemsInSection:(NSInteger)section; - -/** - * Asks the data source for the number of sections in the collection node. - * - * @see @c numberOfSectionsInCollectionView: - */ -- (NSInteger)numberOfSectionsInCollectionNode:(ASCollectionNode *)collectionNode; - -/** - * --BETA-- - * Asks the data source for a view-model for the item at the given index path. - * - * @param collectionNode The sender. - * @param indexPath The index path of the item. - * - * @return An object that contains all the data for this item. - */ -- (nullable id)collectionNode:(ASCollectionNode *)collectionNode nodeModelForItemAtIndexPath:(NSIndexPath *)indexPath; - -/** - * Similar to -collectionNode:nodeForItemAtIndexPath: - * This method takes precedence over collectionNode:nodeForItemAtIndexPath: if implemented. - * - * @param collectionNode The sender. - * @param indexPath The index path of the item. - * - * @return a block that creates the node for display for this item. - * Must be thread-safe (can be called on the main thread or a background - * queue) and should not implement reuse (it will be called once per row). - */ -- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath; - -/** - * Similar to -collectionView:cellForItemAtIndexPath:. - * - * @param collectionNode The sender. - * @param indexPath The index path of the item. - * - * @return A node to display for the given item. This will be called on the main thread and should - * not implement reuse (it will be called once per item). Unlike UICollectionView's version, - * this method is not called when the item is about to display. - */ -- (ASCellNode *)collectionNode:(ASCollectionNode *)collectionNode nodeForItemAtIndexPath:(NSIndexPath *)indexPath; - -/** - * Asks the data source to provide a node-block to display for the given supplementary element in the collection view. - * - * @param collectionNode The sender. - * @param kind The kind of supplementary element. - * @param indexPath The index path of the supplementary element. - */ -- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; - -/** - * Asks the data source to provide a node to display for the given supplementary element in the collection view. - * - * @param collectionNode The sender. - * @param kind The kind of supplementary element. - * @param indexPath The index path of the supplementary element. - */ -- (ASCellNode *)collectionNode:(ASCollectionNode *)collectionNode nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; - -/** - * Asks the data source to provide a context object for the given section. This object - * can later be retrieved by calling @c contextForSection: and is useful when implementing - * custom @c UICollectionViewLayout subclasses. The context object is ret - * - * @param collectionNode The sender. - * @param section The index of the section to provide context for. - * - * @return A context object to assign to the given section, or @c nil. - */ -- (nullable id)collectionNode:(ASCollectionNode *)collectionNode contextForSection:(NSInteger)section; - -/** - * Asks the data source to provide an array of supplementary element kinds that exist in a given section. - * - * @param collectionNode The sender. - * @param section The index of the section to provide supplementary kinds for. - * - * @return The supplementary element kinds that exist in the given section, if any. - */ -- (NSArray *)collectionNode:(ASCollectionNode *)collectionNode supplementaryElementKindsInSection:(NSInteger)section; - -/** - * Asks the data source if it's possible to move the specified item interactively. - * - * See @p -[UICollectionViewDataSource collectionView:canMoveItemAtIndexPath:] @c. - * - * @param collectionNode The sender. - * @param node The display node for the item that may be moved. - * - * @return Whether the item represented by @p node may be moved. - */ -- (BOOL)collectionNode:(ASCollectionNode *)collectionNode canMoveItemWithNode:(ASCellNode *)node; - -/** - * Called when the user has interactively moved an item. The data source - * should update its internal data store to reflect the move. Note that you - * should not call [collectionNode moveItemAtIndexPath:toIndexPath:] – the - * collection node's internal state will be updated automatically. - * - * * See @p -[UICollectionViewDataSource collectionView:moveItemAtIndexPath:toIndexPath:] @c. - * - * @param collectionNode The sender. - * @param sourceIndexPath The original item index path. - * @param destinationIndexPath The new item index path. - */ -- (void)collectionNode:(ASCollectionNode *)collectionNode moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath; - -/** - * Generate a unique identifier for an element in a collection. This helps state restoration persist the scroll position - * of a collection view even when the data in that table changes. See the documentation for UIDataSourceModelAssociation for more information. - * - * @param indexPath The index path of the requested node. - * - * @param collectionNode The sender. - * - * @return a unique identifier for the element at the given path. Return nil if the index path does not exist in the collection. - */ -- (nullable NSString *)modelIdentifierForElementAtIndexPath:(NSIndexPath *)indexPath inNode:(ASCollectionNode *)collectionNode; - -/** - * Similar to -collectionView:cellForItemAtIndexPath:. See the documentation for UIDataSourceModelAssociation for more information. - * - * @param identifier The model identifier of the element, previously generated by a call to modelIdentifierForElementAtIndexPath - * - * @param collectionNode The sender. - * - * @return the index path to the current position of the matching element in the collection. Return nil if the element is not found. - */ -- (nullable NSIndexPath *)indexPathForElementWithModelIdentifier:(NSString *)identifier inNode:(ASCollectionNode *)collectionNode; - -/** - * Similar to -collectionView:cellForItemAtIndexPath:. - * - * @param collectionView The sender. - * - * @param indexPath The index path of the requested node. - * - * @return a node for display at this indexpath. This will be called on the main thread and should - * not implement reuse (it will be called once per row). Unlike UICollectionView's version, - * this method is not called when the row is about to display. - */ -- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead."); - -/** - * Similar to -collectionView:nodeForItemAtIndexPath: - * This method takes precedence over collectionView:nodeForItemAtIndexPath: if implemented. - * - * @param collectionView The sender. - * - * @param indexPath The index path of the requested node. - * - * @return a block that creates the node for display at this indexpath. - * Must be thread-safe (can be called on the main thread or a background - * queue) and should not implement reuse (it will be called once per row). - */ -- (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead."); - -/** - * Asks the collection view to provide a supplementary node to display in the collection view. - * - * @param collectionView An object representing the collection view requesting this information. - * @param kind The kind of supplementary node to provide. - * @param indexPath The index path that specifies the location of the new supplementary node. - */ -- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead."); - -/** - * Indicator to lock the data source for data fetching in async mode. - * We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistency or exception - * due to the data access in async mode. - * - * @param collectionView The sender. - * @deprecated The data source is always accessed on the main thread, and this method will not be called. - */ -- (void)collectionViewLockDataSource:(ASCollectionView *)collectionView ASDISPLAYNODE_DEPRECATED_MSG("Data source accesses are on the main thread. Method will not be called."); - -/** - * Indicator to unlock the data source for data fetching in async mode. - * We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistency or exception - * due to the data access in async mode. - * - * @param collectionView The sender. - * @deprecated The data source is always accessed on the main thread, and this method will not be called. - */ -- (void)collectionViewUnlockDataSource:(ASCollectionView *)collectionView ASDISPLAYNODE_DEPRECATED_MSG("Data source accesses are on the main thread. Method will not be called."); - -@end - -/** - * This is a node-based UICollectionViewDelegate. - */ -@protocol ASCollectionDelegate - -@optional - -/** - * Provides the constrained size range for measuring the given item. - * - * @param collectionNode The sender. - * - * @param indexPath The index path of the item. - * - * @return A constrained size range for layout for the item at this index path. - */ -- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode constrainedSizeForItemAtIndexPath:(NSIndexPath *)indexPath; - -- (void)collectionNode:(ASCollectionNode *)collectionNode willDisplayItemWithNode:(ASCellNode *)node; - -- (void)collectionNode:(ASCollectionNode *)collectionNode didEndDisplayingItemWithNode:(ASCellNode *)node; - -- (void)collectionNode:(ASCollectionNode *)collectionNode willDisplaySupplementaryElementWithNode:(ASCellNode *)node NS_AVAILABLE_IOS(8_0); -- (void)collectionNode:(ASCollectionNode *)collectionNode didEndDisplayingSupplementaryElementWithNode:(ASCellNode *)node; - -- (BOOL)collectionNode:(ASCollectionNode *)collectionNode shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath; -- (void)collectionNode:(ASCollectionNode *)collectionNode didHighlightItemAtIndexPath:(NSIndexPath *)indexPath; -- (void)collectionNode:(ASCollectionNode *)collectionNode didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath; -- (BOOL)collectionNode:(ASCollectionNode *)collectionNode shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath; -- (BOOL)collectionNode:(ASCollectionNode *)collectionNode shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath; -- (void)collectionNode:(ASCollectionNode *)collectionNode didSelectItemAtIndexPath:(NSIndexPath *)indexPath; -- (void)collectionNode:(ASCollectionNode *)collectionNode didDeselectItemAtIndexPath:(NSIndexPath *)indexPath; - -- (BOOL)collectionNode:(ASCollectionNode *)collectionNode shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath; -- (BOOL)collectionNode:(ASCollectionNode *)collectionNode canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath sender:(nullable id)sender; -- (void)collectionNode:(ASCollectionNode *)collectionNode performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath sender:(nullable id)sender; - -/** - * Receive a message that the collection node is near the end of its data set and more data should be fetched if - * necessary. - * - * @param collectionNode The sender. - * @param context A context object that must be notified when the batch fetch is completed. - * - * @discussion You must eventually call -completeBatchFetching: with an argument of YES in order to receive future - * notifications to do batch fetches. This method is called on a background queue. - * - * ASCollectionNode currently only supports batch events for tail loads. If you require a head load, consider - * implementing a UIRefreshControl. - */ -- (void)collectionNode:(ASCollectionNode *)collectionNode willBeginBatchFetchWithContext:(ASBatchContext *)context; - -/** - * Tell the collection node if batch fetching should begin. - * - * @param collectionNode The sender. - * - * @discussion Use this method to conditionally fetch batches. Example use cases are: limiting the total number of - * objects that can be fetched or no network connection. - * - * If not implemented, the collection node assumes that it should notify its asyncDelegate when batch fetching - * should occur. - */ -- (BOOL)shouldBatchFetchForCollectionNode:(ASCollectionNode *)collectionNode; - -/** - * Provides the constrained size range for measuring the node at the index path. - * - * @param collectionView The sender. - * - * @param indexPath The index path of the node. - * - * @return A constrained size range for layout the node at this index path. - */ -- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's constrainedSizeForItemAtIndexPath: instead. PLEASE NOTE the very subtle method name change."); - -/** - * Informs the delegate that the collection view will add the given node - * at the given index path to the view hierarchy. - * - * @param collectionView The sender. - * @param node The node that will be displayed. - * @param indexPath The index path of the item that will be displayed. - * - * @warning AsyncDisplayKit processes collection view edits asynchronously. The index path - * passed into this method may not correspond to the same item in your data source - * if your data source has been updated since the last edit was processed. - */ -- (void)collectionView:(ASCollectionView *)collectionView willDisplayNode:(ASCellNode *)node forItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead."); - -/** - * Informs the delegate that the collection view did remove the provided node from the view hierarchy. - * This may be caused by the node scrolling out of view, or by deleting the item - * or its containing section with @c deleteItemsAtIndexPaths: or @c deleteSections: . - * - * @param collectionView The sender. - * @param node The node which was removed from the view hierarchy. - * @param indexPath The index path at which the node was located before it was removed. - * - * @warning AsyncDisplayKit processes collection view edits asynchronously. The index path - * passed into this method may not correspond to the same item in your data source - * if your data source has been updated since the last edit was processed. - */ -- (void)collectionView:(ASCollectionView *)collectionView didEndDisplayingNode:(ASCellNode *)node forItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead."); - -- (void)collectionView:(ASCollectionView *)collectionView willBeginBatchFetchWithContext:(ASBatchContext *)context ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead."); - -/** - * Tell the collectionView if batch fetching should begin. - * - * @param collectionView The sender. - * - * @discussion Use this method to conditionally fetch batches. Example use cases are: limiting the total number of - * objects that can be fetched or no network connection. - * - * If not implemented, the collectionView assumes that it should notify its asyncDelegate when batch fetching - * should occur. - */ -- (BOOL)shouldBatchFetchForCollectionView:(ASCollectionView *)collectionView ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead."); - -/** - * Informs the delegate that the collection view will add the node - * at the given index path to the view hierarchy. - * - * @param collectionView The sender. - * @param indexPath The index path of the item that will be displayed. - * - * @warning AsyncDisplayKit processes collection view edits asynchronously. The index path - * passed into this method may not correspond to the same item in your data source - * if your data source has been updated since the last edit was processed. - * - * This method is deprecated. Use @c collectionView:willDisplayNode:forItemAtIndexPath: instead. - */ -- (void)collectionView:(ASCollectionView *)collectionView willDisplayNodeForItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead."); - -@end - -@protocol ASCollectionDataSourceInterop - -/** - * This method offers compatibility with synchronous, standard UICollectionViewCell objects. - * These cells will **not** have the performance benefits of ASCellNodes (like preloading, async layout, and - * async drawing) - even when mixed within the same ASCollectionNode. - * - * In order to use this method, you must: - * 1. Implement it on your ASCollectionDataSource object. - * 2. Call registerCellClass: on the collectionNode.view (in viewDidLoad, or register an onDidLoad: block). - * 3. Return nil from the nodeBlockForItem...: or nodeForItem...: method. NOTE: it is an error to return - * nil from within a nodeBlock, if you have returned a nodeBlock object. - * 4. Lastly, you must implement a method to provide the size for the cell. There are two ways this is done: - * 4a. UICollectionViewFlowLayout (incl. ASPagerNode). Implement - collectionNode:constrainedSizeForItemAtIndexPath:. - * 4b. Custom collection layouts. Set .layoutInspector and have it implement - collectionView:constrainedSizeForNodeAtIndexPath:. - * - * For an example of using this method with all steps above (including a custom layout, 4b.), - * see the app in examples/CustomCollectionView and enable kShowUICollectionViewCells = YES. - */ -- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath; - -@optional - -- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; - -/** - * Implement this property and return YES if you want your interop data source to be - * used when dequeuing cells for node-backed items. - * - * If NO (the default), the interop data source will only be consulted in cases - * where no ASCellNode was provided to AsyncDisplayKit. - * - * If YES, the interop data source will always be consulted to dequeue cells, and - * will be expected to return _ASCollectionViewCells in cases where a node was provided. - * - * The default value is NO. - */ -@property (class, nonatomic, readonly) BOOL dequeuesCellsForNodeBackedItems; - -@end - -@protocol ASCollectionDelegateInterop - -@optional - -- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath; - -- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath; - -- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath; - -- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionNode.mm b/submodules/AsyncDisplayKit/Source/ASCollectionNode.mm deleted file mode 100644 index 6068f1f038..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionNode.mm +++ /dev/null @@ -1,1056 +0,0 @@ -// -// ASCollectionNode.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK -#import -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#pragma mark - _ASCollectionPendingState - -@interface _ASCollectionPendingState : NSObject { -@public - std::vector> _tuningParameters; -} -@property (nonatomic, weak) id delegate; -@property (nonatomic, weak) id dataSource; -@property (nonatomic) UICollectionViewLayout *collectionViewLayout; -@property (nonatomic) ASLayoutRangeMode rangeMode; -@property (nonatomic) BOOL allowsSelection; // default is YES -@property (nonatomic) BOOL allowsMultipleSelection; // default is NO -@property (nonatomic) BOOL inverted; //default is NO -@property (nonatomic) ASCellLayoutMode cellLayoutMode; -@property (nonatomic) CGFloat leadingScreensForBatching; -@property (nonatomic, weak) id layoutInspector; -@property (nonatomic) BOOL alwaysBounceVertical; -@property (nonatomic) BOOL alwaysBounceHorizontal; -@property (nonatomic) UIEdgeInsets contentInset; -@property (nonatomic) CGPoint contentOffset; -@property (nonatomic) BOOL animatesContentOffset; -@property (nonatomic) BOOL showsVerticalScrollIndicator; -@property (nonatomic) BOOL showsHorizontalScrollIndicator; -@end - -@implementation _ASCollectionPendingState - -#pragma mark Lifecycle - -- (instancetype)init -{ - self = [super init]; - if (self) { - _rangeMode = ASLayoutRangeModeUnspecified; - _tuningParameters = [ASAbstractLayoutController defaultTuningParameters]; - _allowsSelection = YES; - _allowsMultipleSelection = NO; - _inverted = NO; - _contentInset = UIEdgeInsetsZero; - _contentOffset = CGPointZero; - _animatesContentOffset = NO; - _showsVerticalScrollIndicator = YES; - _showsHorizontalScrollIndicator = YES; - } - return self; -} - -#pragma mark Tuning Parameters - -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType -{ - return [self tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; -} - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType -{ - return [self setTuningParameters:tuningParameters forRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; -} - -- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - ASDisplayNodeAssert(rangeMode < _tuningParameters.size() && rangeType < _tuningParameters[rangeMode].size(), @"Requesting a range that is OOB for the configured tuning parameters"); - return _tuningParameters[rangeMode][rangeType]; -} - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - ASDisplayNodeAssert(rangeMode < _tuningParameters.size() && rangeType < _tuningParameters[rangeMode].size(), @"Setting a range that is OOB for the configured tuning parameters"); - _tuningParameters[rangeMode][rangeType] = tuningParameters; -} - -@end - -#pragma mark - ASCollectionNode - -@interface ASCollectionNode () -{ - AS::RecursiveMutex _environmentStateLock; - Class _collectionViewClass; - id _batchFetchingDelegate; -} -@property (nonatomic) _ASCollectionPendingState *pendingState; -@property (nonatomic, weak) ASRangeController *rangeController; -@end - -@implementation ASCollectionNode - -#pragma mark Lifecycle - -- (Class)collectionViewClass -{ - return _collectionViewClass ? : [ASCollectionView class]; -} - -- (void)setCollectionViewClass:(Class)collectionViewClass -{ - if (_collectionViewClass != collectionViewClass) { - ASDisplayNodeAssert([collectionViewClass isSubclassOfClass:[ASCollectionView class]] || collectionViewClass == Nil, @"ASCollectionNode requires that .collectionViewClass is an ASCollectionView subclass"); - ASDisplayNodeAssert([self isNodeLoaded] == NO, @"ASCollectionNode's .collectionViewClass cannot be changed after the view is loaded"); - _collectionViewClass = collectionViewClass; - } -} - -- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout -{ - return [self initWithFrame:CGRectZero collectionViewLayout:layout layoutFacilitator:nil]; -} - -- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout -{ - return [self initWithFrame:frame collectionViewLayout:layout layoutFacilitator:nil]; -} - -- (instancetype)initWithLayoutDelegate:(id)layoutDelegate layoutFacilitator:(id)layoutFacilitator -{ - return [self initWithFrame:CGRectZero collectionViewLayout:[[ASCollectionLayout alloc] initWithLayoutDelegate:layoutDelegate] layoutFacilitator:layoutFacilitator]; -} - -- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(id)layoutFacilitator -{ - if (self = [super init]) { - // Must call the setter here to make sure pendingState is created and the layout is configured. - [self setCollectionViewLayout:layout]; - - __weak __typeof__(self) weakSelf = self; - [self setViewBlock:^{ - __typeof__(self) strongSelf = weakSelf; - return [[[strongSelf collectionViewClass] alloc] _initWithFrame:frame collectionViewLayout:strongSelf->_pendingState.collectionViewLayout layoutFacilitator:layoutFacilitator owningNode:strongSelf eventLog:ASDisplayNodeGetEventLog(strongSelf)]; - }]; - } - return self; -} - -#if ASDISPLAYNODE_ASSERTIONS_ENABLED -- (void)dealloc -{ - if (self.nodeLoaded) { - __weak UIView *view = self.view; - ASPerformBlockOnMainThread(^{ - ASDisplayNodeCAssertNil(view.superview, @"Node's view should be removed from hierarchy."); - }); - } -} -#endif - -#pragma mark ASDisplayNode - -- (void)didLoad -{ - [super didLoad]; - - ASCollectionView *view = self.view; - view.collectionNode = self; - - _rangeController = view.rangeController; - - if (_pendingState) { - _ASCollectionPendingState *pendingState = _pendingState; - self.pendingState = nil; - view.asyncDelegate = pendingState.delegate; - view.asyncDataSource = pendingState.dataSource; - view.inverted = pendingState.inverted; - view.allowsSelection = pendingState.allowsSelection; - view.allowsMultipleSelection = pendingState.allowsMultipleSelection; - view.cellLayoutMode = pendingState.cellLayoutMode; - view.layoutInspector = pendingState.layoutInspector; - view.showsVerticalScrollIndicator = pendingState.showsVerticalScrollIndicator; - view.showsHorizontalScrollIndicator = pendingState.showsHorizontalScrollIndicator; - - // Only apply these flags if they're enabled; the view might come with them turned on. - if (pendingState.alwaysBounceVertical) { - view.alwaysBounceVertical = YES; - } - if (pendingState.alwaysBounceHorizontal) { - view.alwaysBounceHorizontal = YES; - } - - UIEdgeInsets contentInset = pendingState.contentInset; - if (!UIEdgeInsetsEqualToEdgeInsets(contentInset, UIEdgeInsetsZero)) { - view.contentInset = contentInset; - } - - CGPoint contentOffset = pendingState.contentOffset; - if (!CGPointEqualToPoint(contentOffset, CGPointZero)) { - [view setContentOffset:contentOffset animated:pendingState.animatesContentOffset]; - } - - const auto tuningParametersVector = pendingState->_tuningParameters; - const auto tuningParametersVectorSize = tuningParametersVector.size(); - for (NSInteger rangeMode = 0; rangeMode < tuningParametersVectorSize; rangeMode++) { - const auto tuningparametersRangeModeVector = tuningParametersVector[rangeMode]; - const auto tuningParametersVectorRangeModeSize = tuningparametersRangeModeVector.size(); - for (NSInteger rangeType = 0; rangeType < tuningParametersVectorRangeModeSize; rangeType++) { - ASRangeTuningParameters tuningParameters = tuningparametersRangeModeVector[rangeType]; - [_rangeController setTuningParameters:tuningParameters - forRangeMode:(ASLayoutRangeMode)rangeMode - rangeType:(ASLayoutRangeType)rangeType]; - } - } - - if (pendingState.rangeMode != ASLayoutRangeModeUnspecified) { - [_rangeController updateCurrentRangeWithMode:pendingState.rangeMode]; - } - - // Don't need to set collectionViewLayout to the view as the layout was already used to init the view in view block. - } -} - -- (ASCollectionView *)view -{ - return (ASCollectionView *)[super view]; -} - -- (void)clearContents -{ - [super clearContents]; - [self.rangeController clearContents]; -} - -- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState -{ - [super interfaceStateDidChange:newState fromState:oldState]; - [ASRangeController layoutDebugOverlayIfNeeded]; -} - -- (void)didEnterPreloadState -{ - [super didEnterPreloadState]; - // ASCollectionNode is often nested inside of other collections. In this case, ASHierarchyState's RangeManaged bit will be set. - // Intentionally allocate the view here and trigger a layout pass on it, which in turn will trigger the intial data load. - // We can get rid of this call later when ASDataController, ASRangeController and ASCollectionLayout can operate without the view. - // TODO (ASCL) If this node supports async layout, kick off the initial data load without allocating the view - if (ASHierarchyStateIncludesRangeManaged(self.hierarchyState) && CGRectEqualToRect(self.bounds, CGRectZero) == NO) { - [self.view layoutIfNeeded]; - } -} - -#if ASRangeControllerLoggingEnabled -- (void)didEnterVisibleState -{ - [super didEnterVisibleState]; - NSLog(@"%@ - visible: YES", self); -} - -- (void)didExitVisibleState -{ - [super didExitVisibleState]; - NSLog(@"%@ - visible: NO", self); -} -#endif - -- (void)didExitPreloadState -{ - [super didExitPreloadState]; - [self.rangeController clearPreloadedData]; -} - -#pragma mark Setter / Getter - -// TODO: Implement this without the view. Then revisit ASLayoutElementCollectionTableSetTraitCollection -- (ASDataController *)dataController -{ - return self.view.dataController; -} - -- (_ASCollectionPendingState *)pendingState -{ - if (!_pendingState && ![self isNodeLoaded]) { - self.pendingState = [[_ASCollectionPendingState alloc] init]; - } - ASDisplayNodeAssert(![self isNodeLoaded] || !_pendingState, @"ASCollectionNode should not have a pendingState once it is loaded"); - return _pendingState; -} - -- (void)setInverted:(BOOL)inverted -{ - self.transform = inverted ? CATransform3DMakeScale(1, -1, 1) : CATransform3DIdentity; - if ([self pendingState]) { - _pendingState.inverted = inverted; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); - self.view.inverted = inverted; - } -} - -- (BOOL)inverted -{ - if ([self pendingState]) { - return _pendingState.inverted; - } else { - return self.view.inverted; - } -} - -- (void)setLayoutInspector:(id)layoutInspector -{ - if ([self pendingState]) { - _pendingState.layoutInspector = layoutInspector; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); - self.view.layoutInspector = layoutInspector; - } -} - -- (id)layoutInspector -{ - if ([self pendingState]) { - return _pendingState.layoutInspector; - } else { - return self.view.layoutInspector; - } -} - -- (void)setLeadingScreensForBatching:(CGFloat)leadingScreensForBatching -{ - if ([self pendingState]) { - _pendingState.leadingScreensForBatching = leadingScreensForBatching; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); - self.view.leadingScreensForBatching = leadingScreensForBatching; - } -} - -- (CGFloat)leadingScreensForBatching -{ - if ([self pendingState]) { - return _pendingState.leadingScreensForBatching; - } else { - return self.view.leadingScreensForBatching; - } -} - -- (void)setDelegate:(id )delegate -{ - if ([self pendingState]) { - _pendingState.delegate = delegate; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); - - // Manually trampoline to the main thread. The view requires this be called on main - // and asserting here isn't an option – it is a common pattern for users to clear - // the delegate/dataSource in dealloc, which may be running on a background thread. - // It is important that we avoid retaining self in this block, so that this method is dealloc-safe. - ASCollectionView *view = self.view; - ASPerformBlockOnMainThread(^{ - view.asyncDelegate = delegate; - }); - } -} - -- (id )delegate -{ - if ([self pendingState]) { - return _pendingState.delegate; - } else { - return self.view.asyncDelegate; - } -} - -- (void)setDataSource:(id )dataSource -{ - if ([self pendingState]) { - _pendingState.dataSource = dataSource; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); - // Manually trampoline to the main thread. The view requires this be called on main - // and asserting here isn't an option – it is a common pattern for users to clear - // the delegate/dataSource in dealloc, which may be running on a background thread. - // It is important that we avoid retaining self in this block, so that this method is dealloc-safe. - ASCollectionView *view = self.view; - ASPerformBlockOnMainThread(^{ - view.asyncDataSource = dataSource; - }); - } -} - -- (id )dataSource -{ - if ([self pendingState]) { - return _pendingState.dataSource; - } else { - return self.view.asyncDataSource; - } -} - -- (void)setAllowsSelection:(BOOL)allowsSelection -{ - if ([self pendingState]) { - _pendingState.allowsSelection = allowsSelection; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); - self.view.allowsSelection = allowsSelection; - } -} - -- (BOOL)allowsSelection -{ - if ([self pendingState]) { - return _pendingState.allowsSelection; - } else { - return self.view.allowsSelection; - } -} - -- (void)setAllowsMultipleSelection:(BOOL)allowsMultipleSelection -{ - if ([self pendingState]) { - _pendingState.allowsMultipleSelection = allowsMultipleSelection; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); - self.view.allowsMultipleSelection = allowsMultipleSelection; - } -} - -- (BOOL)allowsMultipleSelection -{ - if ([self pendingState]) { - return _pendingState.allowsMultipleSelection; - } else { - return self.view.allowsMultipleSelection; - } -} - -- (void)setAlwaysBounceVertical:(BOOL)alwaysBounceVertical -{ - if ([self pendingState]) { - _pendingState.alwaysBounceVertical = alwaysBounceVertical; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); - self.view.alwaysBounceVertical = alwaysBounceVertical; - } -} - -- (BOOL)alwaysBounceVertical -{ - if ([self pendingState]) { - return _pendingState.alwaysBounceVertical; - } else { - return self.view.alwaysBounceVertical; - } -} - -- (void)setAlwaysBounceHorizontal:(BOOL)alwaysBounceHorizontal -{ - if ([self pendingState]) { - _pendingState.alwaysBounceHorizontal = alwaysBounceHorizontal; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); - self.view.alwaysBounceHorizontal = alwaysBounceHorizontal; - } -} - -- (BOOL)alwaysBounceHorizontal -{ - if ([self pendingState]) { - return _pendingState.alwaysBounceHorizontal; - } else { - return self.view.alwaysBounceHorizontal; - } -} - -- (void)setShowsVerticalScrollIndicator:(BOOL)showsVerticalScrollIndicator -{ - if ([self pendingState]) { - _pendingState.showsVerticalScrollIndicator = showsVerticalScrollIndicator; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); - self.view.showsVerticalScrollIndicator = showsVerticalScrollIndicator; - } -} - -- (BOOL)showsVerticalScrollIndicator -{ - if ([self pendingState]) { - return _pendingState.showsVerticalScrollIndicator; - } else { - return self.view.showsVerticalScrollIndicator; - } -} - -- (void)setShowsHorizontalScrollIndicator:(BOOL)showsHorizontalScrollIndicator -{ - if ([self pendingState]) { - _pendingState.showsHorizontalScrollIndicator = showsHorizontalScrollIndicator; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); - self.view.showsHorizontalScrollIndicator = showsHorizontalScrollIndicator; - } -} - -- (BOOL)showsHorizontalScrollIndicator -{ - if ([self pendingState]) { - return _pendingState.showsHorizontalScrollIndicator; - } else { - return self.view.showsHorizontalScrollIndicator; - } -} - -- (void)setCollectionViewLayout:(UICollectionViewLayout *)layout -{ - if ([self pendingState]) { - [self _configureCollectionViewLayout:layout]; - _pendingState.collectionViewLayout = layout; - } else { - [self _configureCollectionViewLayout:layout]; - self.view.collectionViewLayout = layout; - } -} - -- (UICollectionViewLayout *)collectionViewLayout -{ - if ([self pendingState]) { - return _pendingState.collectionViewLayout; - } else { - return self.view.collectionViewLayout; - } -} - -- (void)setContentInset:(UIEdgeInsets)contentInset -{ - if ([self pendingState]) { - _pendingState.contentInset = contentInset; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); - self.view.contentInset = contentInset; - } -} - -- (UIEdgeInsets)contentInset -{ - if ([self pendingState]) { - return _pendingState.contentInset; - } else { - return self.view.contentInset; - } -} - -- (void)setContentOffset:(CGPoint)contentOffset -{ - [self setContentOffset:contentOffset animated:NO]; -} - -- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated -{ - if ([self pendingState]) { - _pendingState.contentOffset = contentOffset; - _pendingState.animatesContentOffset = animated; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); - [self.view setContentOffset:contentOffset animated:animated]; - } -} - -- (CGPoint)contentOffset -{ - if ([self pendingState]) { - return _pendingState.contentOffset; - } else { - return self.view.contentOffset; - } -} - -- (ASScrollDirection)scrollDirection -{ - return [self isNodeLoaded] ? self.view.scrollDirection : ASScrollDirectionNone; -} - -- (ASScrollDirection)scrollableDirections -{ - return [self isNodeLoaded] ? self.view.scrollableDirections : ASScrollDirectionNone; -} - -- (ASElementMap *)visibleElements -{ - ASDisplayNodeAssertMainThread(); - // TODO Own the data controller when view is not yet loaded - return self.dataController.visibleMap; -} - -- (id)layoutDelegate -{ - UICollectionViewLayout *layout = self.collectionViewLayout; - if ([layout isKindOfClass:[ASCollectionLayout class]]) { - return ((ASCollectionLayout *)layout).layoutDelegate; - } - return nil; -} - -- (void)setBatchFetchingDelegate:(id)batchFetchingDelegate -{ - _batchFetchingDelegate = batchFetchingDelegate; -} - -- (id)batchFetchingDelegate -{ - return _batchFetchingDelegate; -} - -- (ASCellLayoutMode)cellLayoutMode -{ - if ([self pendingState]) { - return _pendingState.cellLayoutMode; - } else { - return self.view.cellLayoutMode; - } -} - -- (void)setCellLayoutMode:(ASCellLayoutMode)cellLayoutMode -{ - if ([self pendingState]) { - _pendingState.cellLayoutMode = cellLayoutMode; - } else { - self.view.cellLayoutMode = cellLayoutMode; - } -} - -#pragma mark - Range Tuning - -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType -{ - return [self tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; -} - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType -{ - [self setTuningParameters:tuningParameters forRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; -} - -- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - if ([self pendingState]) { - return [_pendingState tuningParametersForRangeMode:rangeMode rangeType:rangeType]; - } else { - return [self.rangeController tuningParametersForRangeMode:rangeMode rangeType:rangeType]; - } -} - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - if ([self pendingState]) { - [_pendingState setTuningParameters:tuningParameters forRangeMode:rangeMode rangeType:rangeType]; - } else { - return [self.rangeController setTuningParameters:tuningParameters forRangeMode:rangeMode rangeType:rangeType]; - } -} - -#pragma mark - Selection - -- (NSArray *)indexPathsForSelectedItems -{ - ASDisplayNodeAssertMainThread(); - ASCollectionView *view = self.view; - return [view convertIndexPathsToCollectionNode:view.indexPathsForSelectedItems]; -} - -- (void)selectItemAtIndexPath:(nullable NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UICollectionViewScrollPosition)scrollPosition -{ - ASDisplayNodeAssertMainThread(); - ASCollectionView *collectionView = self.view; - - indexPath = [collectionView convertIndexPathFromCollectionNode:indexPath waitingIfNeeded:YES]; - - if (indexPath != nil) { - [collectionView selectItemAtIndexPath:indexPath animated:animated scrollPosition:scrollPosition]; - } else { - NSLog(@"Failed to select item at index path %@ because the item never reached the view.", indexPath); - } -} - -- (void)deselectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated -{ - ASDisplayNodeAssertMainThread(); - ASCollectionView *collectionView = self.view; - - indexPath = [collectionView convertIndexPathFromCollectionNode:indexPath waitingIfNeeded:YES]; - - if (indexPath != nil) { - [collectionView deselectItemAtIndexPath:indexPath animated:animated]; - } else { - NSLog(@"Failed to deselect item at index path %@ because the item never reached the view.", indexPath); - } -} - -- (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated -{ - ASDisplayNodeAssertMainThread(); - ASCollectionView *collectionView = self.view; - - indexPath = [collectionView convertIndexPathFromCollectionNode:indexPath waitingIfNeeded:YES]; - - if (indexPath != nil) { - [collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated]; - } else { - NSLog(@"Failed to scroll to item at index path %@ because the item never reached the view.", indexPath); - } -} - -#pragma mark - Querying Data - -- (void)reloadDataInitiallyIfNeeded -{ - if (!self.dataController.initialReloadDataHasBeenCalled) { - [self reloadData]; - } -} - -- (NSInteger)numberOfItemsInSection:(NSInteger)section -{ - [self reloadDataInitiallyIfNeeded]; - return [self.dataController.pendingMap numberOfItemsInSection:section]; -} - -- (NSInteger)numberOfSections -{ - [self reloadDataInitiallyIfNeeded]; - return self.dataController.pendingMap.numberOfSections; -} - -- (NSArray<__kindof ASCellNode *> *)visibleNodes -{ - ASDisplayNodeAssertMainThread(); - return self.isNodeLoaded ? [self.view visibleNodes] : @[]; -} - -- (ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath -{ - [self reloadDataInitiallyIfNeeded]; - return [self.dataController.pendingMap elementForItemAtIndexPath:indexPath].node; -} - -- (id)nodeModelForItemAtIndexPath:(NSIndexPath *)indexPath -{ - [self reloadDataInitiallyIfNeeded]; - return [self.dataController.pendingMap elementForItemAtIndexPath:indexPath].nodeModel; -} - -- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode -{ - return [self.dataController.pendingMap indexPathForElement:cellNode.collectionElement]; -} - -- (NSArray *)indexPathsForVisibleItems -{ - ASDisplayNodeAssertMainThread(); - NSMutableArray *indexPathsArray = [NSMutableArray new]; - for (ASCellNode *cell in [self visibleNodes]) { - NSIndexPath *indexPath = [self indexPathForNode:cell]; - if (indexPath) { - [indexPathsArray addObject:indexPath]; - } - } - return indexPathsArray; -} - -- (nullable NSIndexPath *)indexPathForItemAtPoint:(CGPoint)point -{ - ASDisplayNodeAssertMainThread(); - ASCollectionView *collectionView = self.view; - - NSIndexPath *indexPath = [collectionView indexPathForItemAtPoint:point]; - if (indexPath != nil) { - return [collectionView convertIndexPathToCollectionNode:indexPath]; - } - return indexPath; -} - -- (nullable UICollectionViewCell *)cellForItemAtIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertMainThread(); - ASCollectionView *collectionView = self.view; - - indexPath = [collectionView convertIndexPathFromCollectionNode:indexPath waitingIfNeeded:YES]; - if (indexPath == nil) { - return nil; - } - return [collectionView cellForItemAtIndexPath:indexPath]; -} - -- (id)contextForSection:(NSInteger)section -{ - ASDisplayNodeAssertMainThread(); - return [self.dataController.pendingMap contextForSection:section]; -} - -#pragma mark - Editing - -- (void)registerSupplementaryNodeOfKind:(NSString *)elementKind -{ - [self.view registerSupplementaryNodeOfKind:elementKind]; -} - -- (void)performBatchAnimated:(BOOL)animated updates:(NS_NOESCAPE void (^)())updates completion:(void (^)(BOOL))completion -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view performBatchAnimated:animated updates:updates completion:completion]; - } else { - if (updates) { - updates(); - } - if (completion) { - completion(YES); - } - } -} - -- (void)performBatchUpdates:(NS_NOESCAPE void (^)())updates completion:(void (^)(BOOL))completion -{ - [self performBatchAnimated:UIView.areAnimationsEnabled updates:updates completion:completion]; -} - -- (BOOL)isProcessingUpdates -{ - return (self.nodeLoaded ? [self.view isProcessingUpdates] : NO); -} - -- (void)onDidFinishProcessingUpdates:(void (^)())completion -{ - if (!completion) { - return; - } - if (!self.nodeLoaded) { - completion(); - } else { - [self.view onDidFinishProcessingUpdates:completion]; - } -} - -- (BOOL)isSynchronized -{ - return (self.nodeLoaded ? [self.view isSynchronized] : YES); -} - -- (void)onDidFinishSynchronizing:(void (^)())completion -{ - if (!completion) { - return; - } - if (!self.nodeLoaded) { - completion(); - } else { - [self.view onDidFinishSynchronizing:completion]; - } -} - -- (void)waitUntilAllUpdatesAreProcessed -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view waitUntilAllUpdatesAreCommitted]; - } -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (void)waitUntilAllUpdatesAreCommitted -{ - [self waitUntilAllUpdatesAreProcessed]; -} -#pragma clang diagnostic pop - -- (void)reloadDataWithCompletion:(void (^)())completion -{ - ASDisplayNodeAssertMainThread(); - if (!self.nodeLoaded) { - return; - } - - [self performBatchUpdates:^{ - [self.view.changeSet reloadData]; - } completion:^(BOOL finished){ - if (completion) { - completion(); - } - }]; -} - -- (void)reloadData -{ - [self reloadDataWithCompletion:nil]; -} - -- (void)relayoutItems -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view relayoutItems]; - } -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (void)beginUpdates -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view beginUpdates]; - } -} - -- (void)endUpdatesAnimated:(BOOL)animated -{ - [self endUpdatesAnimated:animated completion:nil]; -} - -- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view endUpdatesAnimated:animated completion:completion]; - } -} -#pragma clang diagnostic pop - -- (void)invalidateFlowLayoutDelegateMetrics { - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view invalidateFlowLayoutDelegateMetrics]; - } -} - -- (void)insertSections:(NSIndexSet *)sections -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view insertSections:sections]; - } -} - -- (void)deleteSections:(NSIndexSet *)sections -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view deleteSections:sections]; - } -} - -- (void)reloadSections:(NSIndexSet *)sections -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view reloadSections:sections]; - } -} - -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view moveSection:section toSection:newSection]; - } -} - -- (void)insertItemsAtIndexPaths:(NSArray *)indexPaths -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view insertItemsAtIndexPaths:indexPaths]; - } -} - -- (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view deleteItemsAtIndexPaths:indexPaths]; - } -} - -- (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view reloadItemsAtIndexPaths:indexPaths]; - } -} - -- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view moveItemAtIndexPath:indexPath toIndexPath:newIndexPath]; - } -} - -#pragma mark - ASRangeControllerUpdateRangeProtocol - -- (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode; -{ - if ([self pendingState]) { - _pendingState.rangeMode = rangeMode; - } else { - [self.rangeController updateCurrentRangeWithMode:rangeMode]; - } -} - -#pragma mark - ASPrimitiveTraitCollection - -ASLayoutElementCollectionTableSetTraitCollection(_environmentStateLock) - -#pragma mark - Debugging (Private) - -- (NSMutableArray *)propertiesForDebugDescription -{ - NSMutableArray *result = [super propertiesForDebugDescription]; - [result addObject:@{ @"dataSource" : ASObjectDescriptionMakeTiny(self.dataSource) }]; - [result addObject:@{ @"delegate" : ASObjectDescriptionMakeTiny(self.delegate) }]; - return result; -} - -#pragma mark - Private methods - -- (void)_configureCollectionViewLayout:(UICollectionViewLayout *)layout -{ - if ([layout isKindOfClass:[ASCollectionLayout class]]) { - ASCollectionLayout *collectionLayout = (ASCollectionLayout *)layout; - collectionLayout.collectionNode = self; - } -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionView.h b/submodules/AsyncDisplayKit/Source/ASCollectionView.h deleted file mode 100644 index 740efc41d9..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionView.h +++ /dev/null @@ -1,496 +0,0 @@ -// -// ASCollectionView.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -#import -#import -#import -#import -#import -#import - -@class ASCellNode; -@class ASCollectionNode; -@protocol ASCollectionDataSource; -@protocol ASCollectionDelegate; -@protocol ASCollectionViewLayoutInspecting; -@protocol ASSectionContext; - -NS_ASSUME_NONNULL_BEGIN - -/** - * Asynchronous UICollectionView with Intelligent Preloading capabilities. - * - * @note ASCollectionNode is strongly recommended over ASCollectionView. This class exists for adoption convenience. - */ -@interface ASCollectionView : UICollectionView - -/** - * Returns the corresponding ASCollectionNode - * - * @return collectionNode The corresponding ASCollectionNode, if one exists. - */ -@property (nonatomic, weak, readonly) ASCollectionNode *collectionNode; - -/** - * Retrieves the node for the item at the given index path. - * - * @param indexPath The index path of the requested node. - * @return The node at the given index path, or @c nil if no item exists at the specified path. - */ -- (nullable ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT; - -/** - * Similar to -indexPathForCell:. - * - * @param cellNode a cellNode in the collection view - * - * @return The index path for this cell node. - * - * @discussion This index path returned by this method is in the _view's_ index space - * and should only be used with @c ASCollectionView directly. To get an index path suitable - * for use with your data source and @c ASCollectionNode, call @c indexPathForNode: on the - * collection node instead. - */ -- (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode AS_WARN_UNUSED_RESULT; - -/** - * Similar to -supplementaryViewForElementKind:atIndexPath: - * - * @param elementKind The kind of supplementary node to locate. - * @param indexPath The index path of the requested supplementary node. - * - * @return The specified supplementary node or @c nil. - */ -- (nullable ASCellNode *)supplementaryNodeForElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT; - -/** - * Retrieves the context object for the given section, as provided by the data source in - * the @c collectionNode:contextForSection: method. This method must be called on the main thread. - * - * @param section The section to get the context for. - * - * @return The context object, or @c nil if no context was provided. - */ -- (nullable id)contextForSection:(NSInteger)section AS_WARN_UNUSED_RESULT; - -@end - -@interface ASCollectionView (Deprecated) - -/* - * A Boolean value that determines whether the nodes that the data source renders will be flipped. - */ -@property (nonatomic) BOOL inverted ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead."); - -/** - * The number of screens left to scroll before the delegate -collectionView:beginBatchFetchingWithContext: is called. - * - * Defaults to two screenfuls. - */ -@property (nonatomic) CGFloat leadingScreensForBatching ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead."); - -/** - * Optional introspection object for the collection view's layout. - * - * @discussion Since supplementary and decoration views are controlled by the collection view's layout, this object - * is used as a bridge to provide information to the internal data controller about the existence of these views and - * their associated index paths. For collection views using `UICollectionViewFlowLayout`, a default inspector - * implementation `ASCollectionViewFlowLayoutInspector` is created and set on this property by default. Custom - * collection view layout subclasses will need to provide their own implementation of an inspector object for their - * supplementary views to be compatible with `ASCollectionView`'s supplementary node support. - */ -@property (nonatomic, weak) id layoutInspector ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead."); - -/** - * Determines collection view's current scroll direction. Supports 2-axis collection views. - * - * @return a bitmask of ASScrollDirection values. - */ -@property (nonatomic, readonly) ASScrollDirection scrollDirection ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead."); - -/** - * Determines collection view's scrollable directions. - * - * @return a bitmask of ASScrollDirection values. - */ -@property (nonatomic, readonly) ASScrollDirection scrollableDirections ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead."); - -/** - * Forces the .contentInset to be UIEdgeInsetsZero. - * - * @discussion By default, UIKit sets the top inset to the navigation bar height, even for horizontally - * scrolling views. This can only be disabled by setting a property on the containing UIViewController, - * automaticallyAdjustsScrollViewInsets, which may not be accessible. ASPagerNode uses this to ensure - * its flow layout behaves predictably and does not log undefined layout warnings. - */ -@property (nonatomic) BOOL zeroContentInsets ASDISPLAYNODE_DEPRECATED_MSG("Set automaticallyAdjustsScrollViewInsets=NO on your view controller instead."); - -/** - * The distance that the content view is inset from the collection view edges. Defaults to UIEdgeInsetsZero. - */ -@property (nonatomic) UIEdgeInsets contentInset ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead"); - -/** - * The point at which the origin of the content view is offset from the origin of the collection view. - */ -@property (nonatomic) CGPoint contentOffset ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead."); - -/** - * The object that acts as the asynchronous delegate of the collection view - * - * @discussion The delegate must adopt the ASCollectionDelegate protocol. The collection view maintains a weak reference to the delegate object. - * - * The delegate object is responsible for providing size constraints for nodes and indicating whether batch fetching should begin. - */ -@property (nonatomic, weak) id asyncDelegate ASDISPLAYNODE_DEPRECATED_MSG("Please use ASCollectionNode's .delegate property instead."); - -/** - * The object that acts as the asynchronous data source of the collection view - * - * @discussion The datasource must adopt the ASCollectionDataSource protocol. The collection view maintains a weak reference to the datasource object. - * - * The datasource object is responsible for providing nodes or node creation blocks to the collection view. - */ -@property (nonatomic, weak) id asyncDataSource ASDISPLAYNODE_DEPRECATED_MSG("Please use ASCollectionNode's .dataSource property instead."); - -/** - * Initializes an ASCollectionView - * - * @discussion Initializes and returns a newly allocated collection view object with the specified layout. - * - * @param layout The layout object to use for organizing items. The collection view stores a strong reference to the specified object. Must not be nil. - */ -- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout ASDISPLAYNODE_DEPRECATED_MSG("Please use ASCollectionNode instead of ASCollectionView."); - -/** - * Initializes an ASCollectionView - * - * @discussion Initializes and returns a newly allocated collection view object with the specified frame and layout. - * - * @param frame The frame rectangle for the collection view, measured in points. The origin of the frame is relative to the superview in which you plan to add it. This frame is passed to the superclass during initialization. - * @param layout The layout object to use for organizing items. The collection view stores a strong reference to the specified object. Must not be nil. - */ -- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout ASDISPLAYNODE_DEPRECATED_MSG("Please use ASCollectionNode instead of ASCollectionView."); - -/** - * Tuning parameters for a range type in full mode. - * - * @param rangeType The range type to get the tuning parameters for. - * - * @return A tuning parameter value for the given range type in full mode. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -/** - * Set the tuning parameters for a range type in full mode. - * - * @param tuningParameters The tuning parameters to store for a range type. - * @param rangeType The range type to set the tuning parameters for. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -/** - * Tuning parameters for a range type in the specified mode. - * - * @param rangeMode The range mode to get the running parameters for. - * @param rangeType The range type to get the tuning parameters for. - * - * @return A tuning parameter value for the given range type in the given mode. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -/** - * Set the tuning parameters for a range type in the specified mode. - * - * @param tuningParameters The tuning parameters to store for a range type. - * @param rangeMode The range mode to set the running parameters for. - * @param rangeType The range type to set the tuning parameters for. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -- (nullable __kindof UICollectionViewCell *)cellForItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -- (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -- (void)selectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UICollectionViewScrollPosition)scrollPosition ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -@property (nonatomic, copy, readonly) NSArray *indexPathsForVisibleItems ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead."); - -@property (nullable, nonatomic, copy, readonly) NSArray *indexPathsForSelectedItems ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead."); - -/** - * Perform a batch of updates asynchronously, optionally disabling all animations in the batch. This method must be called from the main thread. - * The asyncDataSource must be updated to reflect the changes before the update block completes. - * - * @param animated NO to disable animations for this batch - * @param updates The block that performs the relevant insert, delete, reload, or move operations. - * @param completion A completion handler block to execute when all of the operations are finished. This block takes a single - * Boolean parameter that contains the value YES if all of the related animations completed successfully or - * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. - */ -- (void)performBatchAnimated:(BOOL)animated updates:(nullable AS_NOESCAPE void (^)(void))updates completion:(nullable void (^)(BOOL finished))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -/** - * Perform a batch of updates asynchronously. This method must be called from the main thread. - * The asyncDataSource must be updated to reflect the changes before update block completes. - * - * @param updates The block that performs the relevant insert, delete, reload, or move operations. - * @param completion A completion handler block to execute when all of the operations are finished. This block takes a single - * Boolean parameter that contains the value YES if all of the related animations completed successfully or - * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. - */ -- (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)(void))updates completion:(nullable void (^)(BOOL finished))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -/** - * Reload everything from scratch, destroying the working range and all cached nodes. - * - * @param completion block to run on completion of asynchronous loading or nil. If supplied, the block is run on - * the main thread. - * @warning This method is substantially more expensive than UICollectionView's version. - */ -- (void)reloadDataWithCompletion:(nullable void (^)(void))completion AS_UNAVAILABLE("Use ASCollectionNode method instead."); - -/** - * Reload everything from scratch, destroying the working range and all cached nodes. - * - * @warning This method is substantially more expensive than UICollectionView's version. - */ -- (void)reloadData AS_UNAVAILABLE("Use ASCollectionNode method instead."); - -/** - * Triggers a relayout of all nodes. - * - * @discussion This method invalidates and lays out every cell node in the collection. - */ -- (void)relayoutItems ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -/** - * See ASCollectionNode.h for full documentation of these methods. - */ -@property (nonatomic, readonly) BOOL isProcessingUpdates; -- (void)onDidFinishProcessingUpdates:(void (^)(void))completion; -- (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("Use -[ASCollectionNode waitUntilAllUpdatesAreProcessed] instead."); - -/** - * See ASCollectionNode.h for full documentation of these methods. - */ -@property (nonatomic, readonly, getter=isSynchronized) BOOL synchronized; -- (void)onDidFinishSynchronizing:(void (^)(void))completion; - -/** - * Registers the given kind of supplementary node for use in creating node-backed supplementary views. - * - * @param elementKind The kind of supplementary node that will be requested through the data source. - * - * @discussion Use this method to register support for the use of supplementary nodes in place of the default - * `registerClass:forSupplementaryViewOfKind:withReuseIdentifier:` and `registerNib:forSupplementaryViewOfKind:withReuseIdentifier:` - * methods. This method will register an internal backing view that will host the contents of the supplementary nodes - * returned from the data source. - */ -- (void)registerSupplementaryNodeOfKind:(NSString *)elementKind ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -/** - * Inserts one or more sections. - * - * @param sections An index set that specifies the sections to insert. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)insertSections:(NSIndexSet *)sections ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -/** - * Deletes one or more sections. - * - * @param sections An index set that specifies the sections to delete. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)deleteSections:(NSIndexSet *)sections ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -/** - * Reloads the specified sections. - * - * @param sections An index set that specifies the sections to reload. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)reloadSections:(NSIndexSet *)sections ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -/** - * Moves a section to a new location. - * - * @param section The index of the section to move. - * - * @param newSection The index that is the destination of the move for the section. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -/** - * Inserts items at the locations identified by an array of index paths. - * - * @param indexPaths An array of NSIndexPath objects, each representing an item index and section index that together identify an item. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)insertItemsAtIndexPaths:(NSArray *)indexPaths ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -/** - * Deletes the items specified by an array of index paths. - * - * @param indexPaths An array of NSIndexPath objects identifying the items to delete. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -/** - * Reloads the specified items. - * - * @param indexPaths An array of NSIndexPath objects identifying the items to reload. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -/** - * Moves the item at a specified location to a destination location. - * - * @param indexPath The index path identifying the item to move. - * - * @param newIndexPath The index path that is the destination of the move for the item. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -/** - * Query the sized node at @c indexPath for its calculatedSize. - * - * @param indexPath The index path for the node of interest. - * - * This method is deprecated. Call @c calculatedSize on the node of interest instead. First deprecated in version 2.0. - */ -- (CGSize)calculatedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Call -calculatedSize on the node of interest instead."); - -/** - * Similar to -visibleCells. - * - * @return an array containing the nodes being displayed on screen. - */ -- (NSArray<__kindof ASCellNode *> *)visibleNodes AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -@end - -ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASCollectionDataSource.") -@protocol ASCollectionViewDataSource -@end - -ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASCollectionDelegate.") -@protocol ASCollectionViewDelegate -@end - -/** - * Defines methods that let you coordinate a `UICollectionViewFlowLayout` in combination with an `ASCollectionNode`. - */ -@protocol ASCollectionDelegateFlowLayout - -@optional - -/** - * Asks the delegate for the inset that should be applied to the given section. - * - * @see the same method in UICollectionViewDelegate. - */ -- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section; - -/** - * Asks the delegate for the size range that should be used to measure the header in the given flow layout section. - * - * @param collectionNode The sender. - * @param section The section. - * - * @return The size range for the header, or @c ASSizeRangeZero if there is no header in this section. - * - * If you want the header to completely determine its own size, return @c ASSizeRangeUnconstrained. - * - * @note Only the scrollable dimension of the returned size range will be used. In a vertical flow, - * only the height will be used. In a horizontal flow, only the width will be used. The other dimension - * will be constrained to fill the collection node. - * - * @discussion If you do not implement this method, ASDK will fall back to calling @c collectionView:layout:referenceSizeForHeaderInSection: - * and using that as the exact constrained size. If you don't implement that method, ASDK will read the @c headerReferenceSize from the layout. - */ -- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode sizeRangeForHeaderInSection:(NSInteger)section; - -/** - * Asks the delegate for the size range that should be used to measure the footer in the given flow layout section. - * - * @param collectionNode The sender. - * @param section The section. - * - * @return The size range for the footer, or @c ASSizeRangeZero if there is no footer in this section. - * - * If you want the footer to completely determine its own size, return @c ASSizeRangeUnconstrained. - * - * @note Only the scrollable dimension of the returned size range will be used. In a vertical flow, - * only the height will be used. In a horizontal flow, only the width will be used. The other dimension - * will be constrained to fill the collection node. - * - * @discussion If you do not implement this method, ASDK will fall back to calling @c collectionView:layout:referenceSizeForFooterInSection: - * and using that as the exact constrained size. If you don't implement that method, ASDK will read the @c footerReferenceSize from the layout. - */ -- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode sizeRangeForFooterInSection:(NSInteger)section; - -/** - * Asks the delegate for the size of the header in the specified section. - */ -- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:sizeRangeForHeaderInSection: instead."); - -/** - * Asks the delegate for the size of the footer in the specified section. - */ -- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:sizeRangeForFooterInSection: instead."); - -@end - -ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASCollectionDelegateFlowLayout.") -@protocol ASCollectionViewDelegateFlowLayout -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionView.mm b/submodules/AsyncDisplayKit/Source/ASCollectionView.mm deleted file mode 100644 index b619fd53a7..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionView.mm +++ /dev/null @@ -1,2521 +0,0 @@ -// -// ASCollectionView.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// -#ifndef MINIMAL_ASDK -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -/** - * A macro to get self.collectionNode and assign it to a local variable, or return - * the given value if nil. - * - * Previously we would set ASCollectionNode's dataSource & delegate to nil - * during dealloc. However, our asyncDelegate & asyncDataSource must be set on the - * main thread, so if the node is deallocated off-main, we won't learn about the change - * until later on. Since our @c collectionNode parameter to delegate methods (e.g. - * collectionNode:didEndDisplayingItemWithNode:) is nonnull, it's important that we never - * unintentionally pass nil (this will crash in Swift, in production). So we can use - * this macro to ensure that our node is still alive before calling out to the user - * on its behalf. - */ -#define GET_COLLECTIONNODE_OR_RETURN(__var, __val) \ - ASCollectionNode *__var = self.collectionNode; \ - if (__var == nil) { \ - return __val; \ - } - -#define ASFlowLayoutDefault(layout, property, default) \ -({ \ - UICollectionViewFlowLayout *flowLayout = ASDynamicCast(layout, UICollectionViewFlowLayout); \ - flowLayout ? flowLayout.property : default; \ -}) - -// ASCellLayoutMode is an NSUInteger-based NS_OPTIONS field. Be careful with BOOL handling on the -// 32-bit Objective-C runtime, and pattern after ASInterfaceStateIncludesVisible() & friends. -#define ASCellLayoutModeIncludes(layoutMode) ((_cellLayoutMode & layoutMode) == layoutMode) - -/// What, if any, invalidation should we perform during the next -layoutSubviews. -typedef NS_ENUM(NSUInteger, ASCollectionViewInvalidationStyle) { - /// Perform no invalidation. - ASCollectionViewInvalidationStyleNone, - /// Perform invalidation with animation (use an empty batch update). - ASCollectionViewInvalidationStyleWithoutAnimation, - /// Perform invalidation without animation (use -invalidateLayout). - ASCollectionViewInvalidationStyleWithAnimation, -}; - -static const NSUInteger kASCollectionViewAnimationNone = UITableViewRowAnimationNone; - -/// Used for all cells and supplementaries. UICV keys by supp-kind+reuseID so this is plenty. -static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; - -#pragma mark - -#pragma mark ASCollectionView. - -@interface ASCollectionView () { - ASCollectionViewProxy *_proxyDataSource; - ASCollectionViewProxy *_proxyDelegate; - - ASDataController *_dataController; - ASRangeController *_rangeController; - ASCollectionViewLayoutController *_layoutController; - id _defaultLayoutInspector; - __weak id _layoutInspector; - NSHashTable<_ASCollectionViewCell *> *_cellsForVisibilityUpdates; - NSHashTable *_cellsForLayoutUpdates; - id _layoutFacilitator; - CGFloat _leadingScreensForBatching; - - // When we update our data controller in response to an interactive move, - // we don't want to tell the collection view about the change (it knows!) - BOOL _updatingInResponseToInteractiveMove; - BOOL _inverted; - - NSUInteger _superBatchUpdateCount; - BOOL _isDeallocating; - - ASBatchContext *_batchContext; - - CGSize _lastBoundsSizeUsedForMeasuringNodes; - - NSMutableSet *_registeredSupplementaryKinds; - - // CountedSet because UIKit may display the same element in multiple cells e.g. during animations. - NSCountedSet *_visibleElements; - - CGPoint _deceleratingVelocity; - - BOOL _zeroContentInsets; - - ASCollectionViewInvalidationStyle _nextLayoutInvalidationStyle; - - /** - * If YES, the `UICollectionView` will reload its data on next layout pass so we should not forward any updates to it. - - * Rationale: - * In `reloadData`, a collection view invalidates its data and marks itself as needing reload, and waits until `layoutSubviews` to requery its data source. - * This can lead to data inconsistency problems. - * Say you have an empty collection view. You call `reloadData`, then immediately insert an item into your data source and call `insertItemsAtIndexPaths:[0,0]`. - * You will get an assertion failure saying `Invalid number of items in section 0. - * The number of items after the update (1) must be equal to the number of items before the update (1) plus or minus the items added and removed (1 added, 0 removed).` - * The collection view never queried your data source before the update to see that it actually had 0 items. - */ - BOOL _superIsPendingDataLoad; - - /** - * It's important that we always check for batch fetching at least once, but also - * that we do not check for batch fetching for empty updates (as that may cause an infinite - * loop of batch fetching, where the batch completes and performBatchUpdates: is called without - * actually making any changes.) So to handle the case where a collection is completely empty - * (0 sections) we always check at least once after each update (initial reload is the first update.) - */ - BOOL _hasEverCheckedForBatchFetchingDueToUpdate; - - /** - * Set during beginInteractiveMovementForItemAtIndexPath and UIGestureRecognizerStateEnded - * (or UIGestureRecognizerStateFailed, UIGestureRecognizerStateCancelled. - */ - BOOL _reordering; - - /** - * Counter used to keep track of nested batch updates. - */ - NSInteger _batchUpdateCount; - - /** - * Keep a strong reference to node till view is ready to release. - */ - ASCollectionNode *_keepalive_node; - - struct { - unsigned int scrollViewDidScroll:1; - unsigned int scrollViewWillBeginDragging:1; - unsigned int scrollViewDidEndDragging:1; - unsigned int scrollViewWillEndDragging:1; - unsigned int scrollViewDidEndDecelerating:1; - unsigned int collectionViewWillDisplayNodeForItem:1; - unsigned int collectionViewWillDisplayNodeForItemDeprecated:1; - unsigned int collectionViewDidEndDisplayingNodeForItem:1; - unsigned int collectionViewShouldSelectItem:1; - unsigned int collectionViewDidSelectItem:1; - unsigned int collectionViewShouldDeselectItem:1; - unsigned int collectionViewDidDeselectItem:1; - unsigned int collectionViewShouldHighlightItem:1; - unsigned int collectionViewDidHighlightItem:1; - unsigned int collectionViewDidUnhighlightItem:1; - unsigned int collectionViewShouldShowMenuForItem:1; - unsigned int collectionViewCanPerformActionForItem:1; - unsigned int collectionViewPerformActionForItem:1; - unsigned int collectionViewWillBeginBatchFetch:1; - unsigned int shouldBatchFetchForCollectionView:1; - unsigned int collectionNodeWillDisplayItem:1; - unsigned int collectionNodeDidEndDisplayingItem:1; - unsigned int collectionNodeShouldSelectItem:1; - unsigned int collectionNodeDidSelectItem:1; - unsigned int collectionNodeShouldDeselectItem:1; - unsigned int collectionNodeDidDeselectItem:1; - unsigned int collectionNodeShouldHighlightItem:1; - unsigned int collectionNodeDidHighlightItem:1; - unsigned int collectionNodeDidUnhighlightItem:1; - unsigned int collectionNodeShouldShowMenuForItem:1; - unsigned int collectionNodeCanPerformActionForItem:1; - unsigned int collectionNodePerformActionForItem:1; - unsigned int collectionNodeWillBeginBatchFetch:1; - unsigned int collectionNodeWillDisplaySupplementaryElement:1; - unsigned int collectionNodeDidEndDisplayingSupplementaryElement:1; - unsigned int shouldBatchFetchForCollectionNode:1; - - // Interop flags - unsigned int interop:1; - unsigned int interopWillDisplayCell:1; - unsigned int interopDidEndDisplayingCell:1; - unsigned int interopWillDisplaySupplementaryView:1; - unsigned int interopdidEndDisplayingSupplementaryView:1; - } _asyncDelegateFlags; - - struct { - unsigned int collectionViewNodeForItem:1; - unsigned int collectionViewNodeBlockForItem:1; - unsigned int collectionViewNodeForSupplementaryElement:1; - unsigned int numberOfSectionsInCollectionView:1; - unsigned int collectionViewNumberOfItemsInSection:1; - unsigned int collectionNodeNodeForItem:1; - unsigned int collectionNodeNodeBlockForItem:1; - unsigned int nodeModelForItem:1; - unsigned int collectionNodeNodeForSupplementaryElement:1; - unsigned int collectionNodeNodeBlockForSupplementaryElement:1; - unsigned int collectionNodeSupplementaryElementKindsInSection:1; - unsigned int numberOfSectionsInCollectionNode:1; - unsigned int collectionNodeNumberOfItemsInSection:1; - unsigned int collectionNodeContextForSection:1; - unsigned int collectionNodeCanMoveItem:1; - unsigned int collectionNodeMoveItem:1; - - // Whether this data source conforms to ASCollectionDataSourceInterop - unsigned int interop:1; - // Whether this interop data source returns YES from +dequeuesCellsForNodeBackedItems - unsigned int interopAlwaysDequeue:1; - // Whether this interop data source implements viewForSupplementaryElementOfKind: - unsigned int interopViewForSupplementaryElement:1; - unsigned int modelIdentifierMethods:1; // if both modelIdentifierForElementAtIndexPath and indexPathForElementWithModelIdentifier are implemented - } _asyncDataSourceFlags; - - struct { - unsigned int constrainedSizeForSupplementaryNodeOfKindAtIndexPath:1; - unsigned int supplementaryNodesOfKindInSection:1; - unsigned int didChangeCollectionViewDataSource:1; - unsigned int didChangeCollectionViewDelegate:1; - } _layoutInspectorFlags; - - BOOL _hasDataControllerLayoutDelegate; -} - -@end - -@implementation ASCollectionView -{ - __weak id _asyncDelegate; - __weak id _asyncDataSource; -} - -// Using _ASDisplayLayer ensures things like -layout are properly forwarded to ASCollectionNode. -+ (Class)layerClass -{ - return [_ASDisplayLayer class]; -} - -#pragma mark - -#pragma mark Lifecycle. - -- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout -{ - return [self initWithFrame:CGRectZero collectionViewLayout:layout]; -} - -- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout -{ - return [self _initWithFrame:frame collectionViewLayout:layout layoutFacilitator:nil owningNode:nil eventLog:nil]; -} - -- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(id)layoutFacilitator owningNode:(ASCollectionNode *)owningNode eventLog:(ASEventLog *)eventLog -{ - if (!(self = [super initWithFrame:frame collectionViewLayout:layout])) - return nil; - - // Disable UICollectionView prefetching. Use super, because self disables this method. - // Experiments done by Instagram show that this option being YES (default) - // when unused causes a significant hit to scroll performance. - // https://github.com/Instagram/IGListKit/issues/318 - if (AS_AVAILABLE_IOS_TVOS(10, 10)) { - super.prefetchingEnabled = NO; - } - - _layoutController = [[ASCollectionViewLayoutController alloc] initWithCollectionView:self]; - - _rangeController = [[ASRangeController alloc] init]; - _rangeController.dataSource = self; - _rangeController.delegate = self; - _rangeController.layoutController = _layoutController; - - _dataController = [[ASDataController alloc] initWithDataSource:self node:owningNode eventLog:eventLog]; - _dataController.delegate = _rangeController; - - _batchContext = [[ASBatchContext alloc] init]; - - _leadingScreensForBatching = 2.0; - - _lastBoundsSizeUsedForMeasuringNodes = self.bounds.size; - - _layoutFacilitator = layoutFacilitator; - - _proxyDelegate = [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; - super.delegate = (id)_proxyDelegate; - - _proxyDataSource = [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; - super.dataSource = (id)_proxyDataSource; - - _registeredSupplementaryKinds = [[NSMutableSet alloc] init]; - _visibleElements = [[NSCountedSet alloc] init]; - - _cellsForVisibilityUpdates = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality]; - _cellsForLayoutUpdates = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality]; - self.backgroundColor = [UIColor whiteColor]; - - [self registerClass:[_ASCollectionViewCell class] forCellWithReuseIdentifier:kReuseIdentifier]; - - [self _configureCollectionViewLayout:layout]; - - return self; -} - -- (void)dealloc -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeCAssert(_batchUpdateCount == 0, @"ASCollectionView deallocated in the middle of a batch update."); - - // Sometimes the UIKit classes can call back to their delegate even during deallocation, due to animation completion blocks etc. - _isDeallocating = YES; - if (!ASActivateExperimentalFeature(ASExperimentalCollectionTeardown)) { - [self setAsyncDelegate:nil]; - [self setAsyncDataSource:nil]; - } - - // Data controller & range controller may own a ton of nodes, let's deallocate those off-main. - ASPerformBackgroundDeallocation(&_dataController); - ASPerformBackgroundDeallocation(&_rangeController); -} - -#pragma mark - -#pragma mark Overrides. - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -/** - * This method is not available to be called by the public i.e. - * it should only be called by UICollectionView itself. UICollectionView - * does this e.g. during the first layout pass, or if you call -numberOfSections - * before its content is loaded. - */ -- (void)reloadData -{ - [self _superReloadData:nil completion:nil]; - - // UICollectionView calls -reloadData during first layoutSubviews and when the data source changes. - // This fires off the first load of cell nodes. - if (_asyncDataSource != nil && !self.dataController.initialReloadDataHasBeenCalled) { - [self performBatchUpdates:^{ - [_changeSet reloadData]; - } completion:nil]; - } -} -#pragma clang diagnostic pop - -- (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated -{ - if ([self validateIndexPath:indexPath]) { - [super scrollToItemAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated]; - } -} - -- (void)relayoutItems -{ - [_dataController relayoutAllNodesWithInvalidationBlock:^{ - [self.collectionViewLayout invalidateLayout]; - [self invalidateFlowLayoutDelegateMetrics]; - }]; -} - -- (BOOL)isProcessingUpdates -{ - return [_dataController isProcessingUpdates]; -} - -- (void)onDidFinishProcessingUpdates:(void (^)())completion -{ - [_dataController onDidFinishProcessingUpdates:completion]; -} - -- (void)waitUntilAllUpdatesAreCommitted -{ - ASDisplayNodeAssertMainThread(); - if (_batchUpdateCount > 0) { - // This assertion will be enabled soon. - // ASDisplayNodeFailAssert(@"Should not call %@ during batch update", NSStringFromSelector(_cmd)); - return; - } - - [_dataController waitUntilAllUpdatesAreProcessed]; -} - -- (BOOL)isSynchronized -{ - return [_dataController isSynchronized]; -} - -- (void)onDidFinishSynchronizing:(void (^)())completion -{ - [_dataController onDidFinishSynchronizing:completion]; -} - -- (void)setDataSource:(id)dataSource -{ - // UIKit can internally generate a call to this method upon changing the asyncDataSource; only assert for non-nil. We also allow this when we're doing interop. - ASDisplayNodeAssert(_asyncDelegateFlags.interop || dataSource == nil, @"ASCollectionView uses asyncDataSource, not UICollectionView's dataSource property."); -} - -- (void)setDelegate:(id)delegate -{ - // Our UIScrollView superclass sets its delegate to nil on dealloc. Only assert if we get a non-nil value here. We also allow this when we're doing interop. - ASDisplayNodeAssert(_asyncDelegateFlags.interop || delegate == nil, @"ASCollectionView uses asyncDelegate, not UICollectionView's delegate property."); -} - -- (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy -{ - if (proxy == _proxyDelegate) { - [self setAsyncDelegate:nil]; - } else if (proxy == _proxyDataSource) { - [self setAsyncDataSource:nil]; - } -} - -- (id)asyncDataSource -{ - return _asyncDataSource; -} - -- (void)setAsyncDataSource:(id)asyncDataSource -{ - // Changing super.dataSource will trigger a setNeedsLayout, so this must happen on the main thread. - ASDisplayNodeAssertMainThread(); - - // Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle - // the (common) case of nilling the asyncDataSource in the ViewController's dealloc. In this case our _asyncDataSource - // will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to hold a strong - // reference to the old dataSource in this case because calls to ASCollectionViewProxy will start failing and cause crashes. - NS_VALID_UNTIL_END_OF_SCOPE id oldDataSource = super.dataSource; - - if (asyncDataSource == nil) { - _asyncDataSource = nil; - _proxyDataSource = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; - _asyncDataSourceFlags = {}; - - } else { - _asyncDataSource = asyncDataSource; - _proxyDataSource = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; - - _asyncDataSourceFlags.collectionViewNodeForItem = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeForItemAtIndexPath:)]; - _asyncDataSourceFlags.collectionViewNodeBlockForItem = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeBlockForItemAtIndexPath:)]; - _asyncDataSourceFlags.numberOfSectionsInCollectionView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]; - _asyncDataSourceFlags.collectionViewNumberOfItemsInSection = [_asyncDataSource respondsToSelector:@selector(collectionView:numberOfItemsInSection:)]; - _asyncDataSourceFlags.collectionViewNodeForSupplementaryElement = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeForSupplementaryElementOfKind:atIndexPath:)]; - - _asyncDataSourceFlags.collectionNodeNodeForItem = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeForItemAtIndexPath:)]; - _asyncDataSourceFlags.collectionNodeNodeBlockForItem = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeBlockForItemAtIndexPath:)]; - _asyncDataSourceFlags.numberOfSectionsInCollectionNode = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionNode:)]; - _asyncDataSourceFlags.collectionNodeNumberOfItemsInSection = [_asyncDataSource respondsToSelector:@selector(collectionNode:numberOfItemsInSection:)]; - _asyncDataSourceFlags.collectionNodeContextForSection = [_asyncDataSource respondsToSelector:@selector(collectionNode:contextForSection:)]; - _asyncDataSourceFlags.collectionNodeNodeForSupplementaryElement = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeForSupplementaryElementOfKind:atIndexPath:)]; - _asyncDataSourceFlags.collectionNodeNodeBlockForSupplementaryElement = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeBlockForSupplementaryElementOfKind:atIndexPath:)]; - _asyncDataSourceFlags.collectionNodeSupplementaryElementKindsInSection = [_asyncDataSource respondsToSelector:@selector(collectionNode:supplementaryElementKindsInSection:)]; - _asyncDataSourceFlags.nodeModelForItem = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeModelForItemAtIndexPath:)]; - _asyncDataSourceFlags.collectionNodeCanMoveItem = [_asyncDataSource respondsToSelector:@selector(collectionNode:canMoveItemWithNode:)]; - _asyncDataSourceFlags.collectionNodeMoveItem = [_asyncDataSource respondsToSelector:@selector(collectionNode:moveItemAtIndexPath:toIndexPath:)]; - - _asyncDataSourceFlags.interop = [_asyncDataSource conformsToProtocol:@protocol(ASCollectionDataSourceInterop)]; - if (_asyncDataSourceFlags.interop) { - id interopDataSource = (id)_asyncDataSource; - _asyncDataSourceFlags.interopAlwaysDequeue = [[interopDataSource class] respondsToSelector:@selector(dequeuesCellsForNodeBackedItems)] && [[interopDataSource class] dequeuesCellsForNodeBackedItems]; - _asyncDataSourceFlags.interopViewForSupplementaryElement = [interopDataSource respondsToSelector:@selector(collectionView:viewForSupplementaryElementOfKind:atIndexPath:)]; - } - - _asyncDataSourceFlags.modelIdentifierMethods = [_asyncDataSource respondsToSelector:@selector(modelIdentifierForElementAtIndexPath:inNode:)] && [_asyncDataSource respondsToSelector:@selector(indexPathForElementWithModelIdentifier:inNode:)]; - - - ASDisplayNodeAssert(_asyncDataSourceFlags.collectionNodeNumberOfItemsInSection || _asyncDataSourceFlags.collectionViewNumberOfItemsInSection, @"Data source must implement collectionNode:numberOfItemsInSection:"); - ASDisplayNodeAssert(_asyncDataSourceFlags.collectionNodeNodeBlockForItem - || _asyncDataSourceFlags.collectionNodeNodeForItem - || _asyncDataSourceFlags.collectionViewNodeBlockForItem - || _asyncDataSourceFlags.collectionViewNodeForItem, @"Data source must implement collectionNode:nodeBlockForItemAtIndexPath: or collectionNode:nodeForItemAtIndexPath:"); - } - - _dataController.validationErrorSource = asyncDataSource; - super.dataSource = (id)_proxyDataSource; - - //Cache results of layoutInspector to ensure flags are up to date if getter lazily loads a new one. - id layoutInspector = self.layoutInspector; - if (_layoutInspectorFlags.didChangeCollectionViewDataSource) { - [layoutInspector didChangeCollectionViewDataSource:asyncDataSource]; - } - [self _asyncDelegateOrDataSourceDidChange]; -} - -- (id)asyncDelegate -{ - return _asyncDelegate; -} - -- (void)setAsyncDelegate:(id)asyncDelegate -{ - // Changing super.delegate will trigger a setNeedsLayout, so this must happen on the main thread. - ASDisplayNodeAssertMainThread(); - - // Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle - // the (common) case of nilling the asyncDelegate in the ViewController's dealloc. In this case our _asyncDelegate - // will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to hold a strong - // reference to the old delegate in this case because calls to ASCollectionViewProxy will start failing and cause crashes. - NS_VALID_UNTIL_END_OF_SCOPE id oldDelegate = super.delegate; - - if (asyncDelegate == nil) { - _asyncDelegate = nil; - _proxyDelegate = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; - _asyncDelegateFlags = {}; - } else { - _asyncDelegate = asyncDelegate; - _proxyDelegate = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self]; - - _asyncDelegateFlags.scrollViewDidScroll = [_asyncDelegate respondsToSelector:@selector(scrollViewDidScroll:)]; - _asyncDelegateFlags.scrollViewWillEndDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]; - _asyncDelegateFlags.scrollViewDidEndDecelerating = [_asyncDelegate respondsToSelector:@selector(scrollViewDidEndDecelerating:)]; - _asyncDelegateFlags.scrollViewWillBeginDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewWillBeginDragging:)]; - _asyncDelegateFlags.scrollViewDidEndDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewDidEndDragging:willDecelerate:)]; - _asyncDelegateFlags.collectionViewWillDisplayNodeForItem = [_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNode:forItemAtIndexPath:)]; - if (_asyncDelegateFlags.collectionViewWillDisplayNodeForItem == NO) { - _asyncDelegateFlags.collectionViewWillDisplayNodeForItemDeprecated = [_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNodeForItemAtIndexPath:)]; - } - _asyncDelegateFlags.collectionViewDidEndDisplayingNodeForItem = [_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNode:forItemAtIndexPath:)]; - _asyncDelegateFlags.collectionViewWillBeginBatchFetch = [_asyncDelegate respondsToSelector:@selector(collectionView:willBeginBatchFetchWithContext:)]; - _asyncDelegateFlags.shouldBatchFetchForCollectionView = [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForCollectionView:)]; - _asyncDelegateFlags.collectionViewShouldSelectItem = [_asyncDelegate respondsToSelector:@selector(collectionView:shouldSelectItemAtIndexPath:)]; - _asyncDelegateFlags.collectionViewDidSelectItem = [_asyncDelegate respondsToSelector:@selector(collectionView:didSelectItemAtIndexPath:)]; - _asyncDelegateFlags.collectionViewShouldDeselectItem = [_asyncDelegate respondsToSelector:@selector(collectionView:shouldDeselectItemAtIndexPath:)]; - _asyncDelegateFlags.collectionViewDidDeselectItem = [_asyncDelegate respondsToSelector:@selector(collectionView:didDeselectItemAtIndexPath:)]; - _asyncDelegateFlags.collectionViewShouldHighlightItem = [_asyncDelegate respondsToSelector:@selector(collectionView:shouldHighlightItemAtIndexPath:)]; - _asyncDelegateFlags.collectionViewDidHighlightItem = [_asyncDelegate respondsToSelector:@selector(collectionView:didHighlightItemAtIndexPath:)]; - _asyncDelegateFlags.collectionViewDidUnhighlightItem = [_asyncDelegate respondsToSelector:@selector(collectionView:didUnhighlightItemAtIndexPath:)]; - _asyncDelegateFlags.collectionViewShouldShowMenuForItem = [_asyncDelegate respondsToSelector:@selector(collectionView:shouldShowMenuForItemAtIndexPath:)]; - _asyncDelegateFlags.collectionViewCanPerformActionForItem = [_asyncDelegate respondsToSelector:@selector(collectionView:canPerformAction:forItemAtIndexPath:withSender:)]; - _asyncDelegateFlags.collectionViewPerformActionForItem = [_asyncDelegate respondsToSelector:@selector(collectionView:performAction:forItemAtIndexPath:withSender:)]; - _asyncDelegateFlags.collectionNodeWillDisplayItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:willDisplayItemWithNode:)]; - _asyncDelegateFlags.collectionNodeDidEndDisplayingItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:didEndDisplayingItemWithNode:)]; - _asyncDelegateFlags.collectionNodeWillBeginBatchFetch = [_asyncDelegate respondsToSelector:@selector(collectionNode:willBeginBatchFetchWithContext:)]; - _asyncDelegateFlags.shouldBatchFetchForCollectionNode = [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForCollectionNode:)]; - _asyncDelegateFlags.collectionNodeShouldSelectItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:shouldSelectItemAtIndexPath:)]; - _asyncDelegateFlags.collectionNodeDidSelectItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:didSelectItemAtIndexPath:)]; - _asyncDelegateFlags.collectionNodeShouldDeselectItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:shouldDeselectItemAtIndexPath:)]; - _asyncDelegateFlags.collectionNodeDidDeselectItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:didDeselectItemAtIndexPath:)]; - _asyncDelegateFlags.collectionNodeShouldHighlightItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:shouldHighlightItemAtIndexPath:)]; - _asyncDelegateFlags.collectionNodeDidHighlightItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:didHighlightItemAtIndexPath:)]; - _asyncDelegateFlags.collectionNodeDidUnhighlightItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:didUnhighlightItemAtIndexPath:)]; - _asyncDelegateFlags.collectionNodeShouldShowMenuForItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:shouldShowMenuForItemAtIndexPath:)]; - _asyncDelegateFlags.collectionNodeCanPerformActionForItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:canPerformAction:forItemAtIndexPath:sender:)]; - _asyncDelegateFlags.collectionNodePerformActionForItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:performAction:forItemAtIndexPath:sender:)]; - _asyncDelegateFlags.collectionNodeWillDisplaySupplementaryElement = [_asyncDelegate respondsToSelector:@selector(collectionNode:willDisplaySupplementaryElementWithNode:)]; - _asyncDelegateFlags.collectionNodeDidEndDisplayingSupplementaryElement = [_asyncDelegate respondsToSelector:@selector(collectionNode:didEndDisplayingSupplementaryElementWithNode:)]; - _asyncDelegateFlags.interop = [_asyncDelegate conformsToProtocol:@protocol(ASCollectionDelegateInterop)]; - if (_asyncDelegateFlags.interop) { - id interopDelegate = (id)_asyncDelegate; - _asyncDelegateFlags.interopWillDisplayCell = [interopDelegate respondsToSelector:@selector(collectionView:willDisplayCell:forItemAtIndexPath:)]; - _asyncDelegateFlags.interopDidEndDisplayingCell = [interopDelegate respondsToSelector:@selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:)]; - _asyncDelegateFlags.interopWillDisplaySupplementaryView = [interopDelegate respondsToSelector:@selector(collectionView:willDisplaySupplementaryView:forElementKind:atIndexPath:)]; - _asyncDelegateFlags.interopdidEndDisplayingSupplementaryView = [interopDelegate respondsToSelector:@selector(collectionView:didEndDisplayingSupplementaryView:forElementOfKind:atIndexPath:)]; - } - } - - super.delegate = (id)_proxyDelegate; - - //Cache results of layoutInspector to ensure flags are up to date if getter lazily loads a new one. - id layoutInspector = self.layoutInspector; - if (_layoutInspectorFlags.didChangeCollectionViewDelegate) { - [layoutInspector didChangeCollectionViewDelegate:asyncDelegate]; - } - [self _asyncDelegateOrDataSourceDidChange]; -} - -- (void)_asyncDelegateOrDataSourceDidChange -{ - ASDisplayNodeAssertMainThread(); - - if (_asyncDataSource == nil && _asyncDelegate == nil && !ASActivateExperimentalFeature(ASExperimentalSkipClearData)) { - [_dataController clearData]; - } -} - -- (void)setCollectionViewLayout:(nonnull UICollectionViewLayout *)collectionViewLayout -{ - ASDisplayNodeAssertMainThread(); - [super setCollectionViewLayout:collectionViewLayout]; - - [self _configureCollectionViewLayout:collectionViewLayout]; - - // Trigger recreation of layout inspector with new collection view layout - if (_layoutInspector != nil) { - _layoutInspector = nil; - [self layoutInspector]; - } -} - -- (id)layoutInspector -{ - if (_layoutInspector == nil) { - UICollectionViewLayout *layout = self.collectionViewLayout; - if (layout == nil) { - // Layout hasn't been set yet, we're still init'ing - return nil; - } - - _defaultLayoutInspector = [layout asdk_layoutInspector]; - ASDisplayNodeAssertNotNil(_defaultLayoutInspector, @"You must not return nil from -asdk_layoutInspector. Return [super asdk_layoutInspector] if you have to! Layout: %@", layout); - - // Explicitly call the setter to wire up the _layoutInspectorFlags - self.layoutInspector = _defaultLayoutInspector; - } - - return _layoutInspector; -} - -- (void)setLayoutInspector:(id)layoutInspector -{ - _layoutInspector = layoutInspector; - - _layoutInspectorFlags.constrainedSizeForSupplementaryNodeOfKindAtIndexPath = [_layoutInspector respondsToSelector:@selector(collectionView:constrainedSizeForSupplementaryNodeOfKind:atIndexPath:)]; - _layoutInspectorFlags.supplementaryNodesOfKindInSection = [_layoutInspector respondsToSelector:@selector(collectionView:supplementaryNodesOfKind:inSection:)]; - _layoutInspectorFlags.didChangeCollectionViewDataSource = [_layoutInspector respondsToSelector:@selector(didChangeCollectionViewDataSource:)]; - _layoutInspectorFlags.didChangeCollectionViewDelegate = [_layoutInspector respondsToSelector:@selector(didChangeCollectionViewDelegate:)]; - - if (_layoutInspectorFlags.didChangeCollectionViewDataSource) { - [_layoutInspector didChangeCollectionViewDataSource:self.asyncDataSource]; - } - if (_layoutInspectorFlags.didChangeCollectionViewDelegate) { - [_layoutInspector didChangeCollectionViewDelegate:self.asyncDelegate]; - } -} - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType -{ - [_rangeController setTuningParameters:tuningParameters forRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; -} - -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType -{ - return [_rangeController tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; -} - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - [_rangeController setTuningParameters:tuningParameters forRangeMode:rangeMode rangeType:rangeType]; -} - -- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - return [_rangeController tuningParametersForRangeMode:rangeMode rangeType:rangeType]; -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (void)setZeroContentInsets:(BOOL)zeroContentInsets -{ - _zeroContentInsets = zeroContentInsets; -} - -- (BOOL)zeroContentInsets -{ - return _zeroContentInsets; -} -#pragma clang diagnostic pop - -/// Uses latest size range from data source and -layoutThatFits:. -- (CGSize)sizeForElement:(ASCollectionElement *)element -{ - ASDisplayNodeAssertMainThread(); - if (element == nil) { - return CGSizeZero; - } - - ASCellNode *node = element.node; - ASDisplayNodeAssertNotNil(node, @"Node must not be nil!"); - - BOOL useUIKitCell = node.shouldUseUIKitCell; - if (useUIKitCell) { - ASWrapperCellNode *wrapperNode = (ASWrapperCellNode *)node; - if (wrapperNode.sizeForItemBlock) { - return wrapperNode.sizeForItemBlock(wrapperNode, element.constrainedSize.max); - } else { - // In this case, it is possible the model indexPath for this element will be nil. Attempt to convert it, - // and call out to the delegate directly. If it has been deleted from the model, the size returned will be the layout's default. - NSIndexPath *indexPath = [_dataController.visibleMap indexPathForElement:element]; - return [self _sizeForUIKitCellWithKind:element.supplementaryElementKind atIndexPath:indexPath]; - } - } else { - return [node layoutThatFits:element.constrainedSize].size; - } -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (CGSize)calculatedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertMainThread(); - - ASCollectionElement *e = [_dataController.visibleMap elementForItemAtIndexPath:indexPath]; - return [self sizeForElement:e]; -} -#pragma clang diagnostic pop - -- (ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath -{ - return [_dataController.visibleMap elementForItemAtIndexPath:indexPath].node; -} - -- (NSIndexPath *)convertIndexPathFromCollectionNode:(NSIndexPath *)indexPath waitingIfNeeded:(BOOL)wait -{ - if (indexPath == nil) { - return nil; - } - - NSIndexPath *viewIndexPath = [_dataController.visibleMap convertIndexPath:indexPath fromMap:_dataController.pendingMap]; - if (viewIndexPath == nil && wait) { - [self waitUntilAllUpdatesAreCommitted]; - return [self convertIndexPathFromCollectionNode:indexPath waitingIfNeeded:NO]; - } - return viewIndexPath; -} - -/** - * Asserts that the index path is a valid view-index-path, and returns it if so, nil otherwise. - */ -- (nullable NSIndexPath *)validateIndexPath:(nullable NSIndexPath *)indexPath -{ - if (indexPath == nil) { - return nil; - } - - NSInteger section = indexPath.section; - if (section >= self.numberOfSections) { - ASDisplayNodeFailAssert(@"Collection view index path has invalid section %lu, section count = %lu", (unsigned long)section, (unsigned long)self.numberOfSections); - return nil; - } - - NSInteger item = indexPath.item; - // item == NSNotFound means e.g. "scroll to this section" and is acceptable - if (item != NSNotFound && item >= [self numberOfItemsInSection:section]) { - ASDisplayNodeFailAssert(@"Collection view index path has invalid item %lu in section %lu, item count = %lu", (unsigned long)indexPath.item, (unsigned long)section, (unsigned long)[self numberOfItemsInSection:section]); - return nil; - } - - return indexPath; -} - -- (NSIndexPath *)convertIndexPathToCollectionNode:(NSIndexPath *)indexPath -{ - if ([self validateIndexPath:indexPath] == nil) { - return nil; - } - - return [_dataController.pendingMap convertIndexPath:indexPath fromMap:_dataController.visibleMap]; -} - -- (NSArray *)convertIndexPathsToCollectionNode:(NSArray *)indexPaths -{ - return ASArrayByFlatMapping(indexPaths, NSIndexPath *viewIndexPath, [self convertIndexPathToCollectionNode:viewIndexPath]); -} - -- (ASCellNode *)supplementaryNodeForElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath -{ - return [_dataController.visibleMap supplementaryElementOfKind:elementKind atIndexPath:indexPath].node; -} - -- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode -{ - return [_dataController.visibleMap indexPathForElement:cellNode.collectionElement]; -} - -- (NSArray *)visibleNodes -{ - NSArray *indexPaths = [self indexPathsForVisibleItems]; - NSMutableArray *visibleNodes = [[NSMutableArray alloc] init]; - - for (NSIndexPath *indexPath in indexPaths) { - ASCellNode *node = [self nodeForItemAtIndexPath:indexPath]; - if (node) { - // It is possible for UICollectionView to return indexPaths before the node is completed. - [visibleNodes addObject:node]; - } - } - - return visibleNodes; -} - -- (void)invalidateFlowLayoutDelegateMetrics -{ - // Subclass hook -} - -- (nullable NSString *)modelIdentifierForElementAtIndexPath:(NSIndexPath *)indexPath inView:(UIView *)view { - if (_asyncDataSourceFlags.modelIdentifierMethods) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, nil); - NSIndexPath *convertedPath = [self convertIndexPathToCollectionNode:indexPath]; - if (convertedPath == nil) { - return nil; - } else { - return [_asyncDataSource modelIdentifierForElementAtIndexPath:convertedPath inNode:collectionNode]; - } - } else { - return nil; - } -} - -- (nullable NSIndexPath *)indexPathForElementWithModelIdentifier:(NSString *)identifier inView:(UIView *)view { - if (_asyncDataSourceFlags.modelIdentifierMethods) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, nil); - return [_asyncDataSource indexPathForElementWithModelIdentifier:identifier inNode:collectionNode]; - } else { - return nil; - } -} - -#pragma mark Internal - -- (void)_configureCollectionViewLayout:(nonnull UICollectionViewLayout *)layout -{ - _hasDataControllerLayoutDelegate = [layout conformsToProtocol:@protocol(ASDataControllerLayoutDelegate)]; - if (_hasDataControllerLayoutDelegate) { - _dataController.layoutDelegate = (id)layout; - } -} - -/** - This method is called only for UIKit Passthrough cells - either regular Items or Supplementary elements. - It checks if the delegate implements the UICollectionViewFlowLayout methods that provide sizes, and if not, - uses the default values set on the flow layout. If a flow layout is not in use, UICollectionView Passthrough - cells must be sized by logic in the Layout object, and Texture does not participate in these paths. -*/ -- (CGSize)_sizeForUIKitCellWithKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - CGSize size = CGSizeZero; - UICollectionViewLayout *l = self.collectionViewLayout; - - if (kind == nil) { - ASDisplayNodeAssert(_asyncDataSourceFlags.interop, @"This code should not be called except for UIKit passthrough compatibility"); - SEL sizeForItem = @selector(collectionView:layout:sizeForItemAtIndexPath:); - if (indexPath && [_asyncDelegate respondsToSelector:sizeForItem]) { - size = [(id)_asyncDelegate collectionView:self layout:l sizeForItemAtIndexPath:indexPath]; - } else { - size = ASFlowLayoutDefault(l, itemSize, CGSizeZero); - } - } else if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { - ASDisplayNodeAssert(_asyncDataSourceFlags.interopViewForSupplementaryElement, @"This code should not be called except for UIKit passthrough compatibility"); - SEL sizeForHeader = @selector(collectionView:layout:referenceSizeForHeaderInSection:); - if (indexPath && [_asyncDelegate respondsToSelector:sizeForHeader]) { - size = [(id)_asyncDelegate collectionView:self layout:l referenceSizeForHeaderInSection:indexPath.section]; - } else { - size = ASFlowLayoutDefault(l, headerReferenceSize, CGSizeZero); - } - } else if ([kind isEqualToString:UICollectionElementKindSectionFooter]) { - ASDisplayNodeAssert(_asyncDataSourceFlags.interopViewForSupplementaryElement, @"This code should not be called except for UIKit passthrough compatibility"); - SEL sizeForFooter = @selector(collectionView:layout:referenceSizeForFooterInSection:); - if (indexPath && [_asyncDelegate respondsToSelector:sizeForFooter]) { - size = [(id)_asyncDelegate collectionView:self layout:l referenceSizeForFooterInSection:indexPath.section]; - } else { - size = ASFlowLayoutDefault(l, footerReferenceSize, CGSizeZero); - } - } - - return size; -} - -- (void)_superReloadData:(void(^)())updates completion:(void(^)(BOOL finished))completion -{ - if (updates) { - updates(); - } - [super reloadData]; - if (completion) { - completion(YES); - } -} - -/** - * Performing nested batch updates with super (e.g. resizing a cell node & updating collection view - * during same frame) can cause super to throw data integrity exceptions because it checks the data - * source counts before the update is complete. - * - * Always call [self _superPerform:] rather than [super performBatch:] so that we can keep our - * `superPerformingBatchUpdates` flag updated. -*/ -- (void)_superPerformBatchUpdates:(void(^)())updates completion:(void(^)(BOOL finished))completion -{ - ASDisplayNodeAssertMainThread(); - - _superBatchUpdateCount++; - [super performBatchUpdates:updates completion:completion]; - _superBatchUpdateCount--; -} - -#pragma mark Assertions. - -- (ASDataController *)dataController -{ - return _dataController; -} - -- (void)beginUpdates -{ - ASDisplayNodeAssertMainThread(); - // _changeSet must be available during batch update - ASDisplayNodeAssertTrue((_batchUpdateCount > 0) == (_changeSet != nil)); - - if (_batchUpdateCount == 0) { - _changeSet = [[_ASHierarchyChangeSet alloc] initWithOldData:[_dataController itemCountsFromDataSource]]; - _changeSet.rootActivity = as_activity_create("Perform async collection update", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT); - _changeSet.submitActivity = as_activity_create("Submit changes for collection update", _changeSet.rootActivity, OS_ACTIVITY_FLAG_DEFAULT); - } - _batchUpdateCount++; -} - -- (void)endUpdatesAnimated:(BOOL)animated completion:(nullable void (^)(BOOL))completion -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssertNotNil(_changeSet, @"_changeSet must be available when batch update ends"); - - _batchUpdateCount--; - // Prevent calling endUpdatesAnimated:completion: in an unbalanced way - NSAssert(_batchUpdateCount >= 0, @"endUpdatesAnimated:completion: called without having a balanced beginUpdates call"); - - [_changeSet addCompletionHandler:completion]; - - if (_batchUpdateCount == 0) { - _ASHierarchyChangeSet *changeSet = _changeSet; - - // Nil out _changeSet before forwarding to _dataController to allow the change set to cause subsequent batch updates on the same run loop - _changeSet = nil; - changeSet.animated = animated; - [_dataController updateWithChangeSet:changeSet]; - } -} - -- (void)performBatchAnimated:(BOOL)animated updates:(NS_NOESCAPE void (^)())updates completion:(void (^)(BOOL))completion -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - as_activity_scope(_changeSet.rootActivity); - { - // Only include client code in the submit activity, the rest just lives in the root activity. - as_activity_scope(_changeSet.submitActivity); - if (updates) { - updates(); - } - } - [self endUpdatesAnimated:animated completion:completion]; -} - -- (void)performBatchUpdates:(NS_NOESCAPE void (^)())updates completion:(void (^)(BOOL))completion -{ - // We capture the current state of whether animations are enabled if they don't provide us with one. - [self performBatchAnimated:[UIView areAnimationsEnabled] updates:updates completion:completion]; -} - -- (void)registerSupplementaryNodeOfKind:(NSString *)elementKind -{ - ASDisplayNodeAssert(elementKind != nil, @"A kind is needed for supplementary node registration"); - [_registeredSupplementaryKinds addObject:elementKind]; - [self registerClass:[_ASCollectionReusableView class] forSupplementaryViewOfKind:elementKind withReuseIdentifier:kReuseIdentifier]; -} - -- (void)insertSections:(NSIndexSet *)sections -{ - ASDisplayNodeAssertMainThread(); - if (sections.count == 0) { return; } - [self performBatchUpdates:^{ - [_changeSet insertSections:sections animationOptions:kASCollectionViewAnimationNone]; - } completion:nil]; -} - -- (void)deleteSections:(NSIndexSet *)sections -{ - ASDisplayNodeAssertMainThread(); - if (sections.count == 0) { return; } - [self performBatchUpdates:^{ - [_changeSet deleteSections:sections animationOptions:kASCollectionViewAnimationNone]; - } completion:nil]; -} - -- (void)reloadSections:(NSIndexSet *)sections -{ - ASDisplayNodeAssertMainThread(); - if (sections.count == 0) { return; } - [self performBatchUpdates:^{ - [_changeSet reloadSections:sections animationOptions:kASCollectionViewAnimationNone]; - } completion:nil]; -} - -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection -{ - ASDisplayNodeAssertMainThread(); - [self performBatchUpdates:^{ - [_changeSet moveSection:section toSection:newSection animationOptions:kASCollectionViewAnimationNone]; - } completion:nil]; -} - -- (id)contextForSection:(NSInteger)section -{ - ASDisplayNodeAssertMainThread(); - return [_dataController.visibleMap contextForSection:section]; -} - -- (void)insertItemsAtIndexPaths:(NSArray *)indexPaths -{ - ASDisplayNodeAssertMainThread(); - if (indexPaths.count == 0) { return; } - [self performBatchUpdates:^{ - [_changeSet insertItems:indexPaths animationOptions:kASCollectionViewAnimationNone]; - } completion:nil]; -} - -- (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths -{ - ASDisplayNodeAssertMainThread(); - if (indexPaths.count == 0) { return; } - [self performBatchUpdates:^{ - [_changeSet deleteItems:indexPaths animationOptions:kASCollectionViewAnimationNone]; - } completion:nil]; -} - -- (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths -{ - ASDisplayNodeAssertMainThread(); - if (indexPaths.count == 0) { return; } - [self performBatchUpdates:^{ - [_changeSet reloadItems:indexPaths animationOptions:kASCollectionViewAnimationNone]; - } completion:nil]; -} - -- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath -{ - ASDisplayNodeAssertMainThread(); - if (!_reordering) { - [self performBatchUpdates:^{ - [_changeSet moveItemAtIndexPath:indexPath toIndexPath:newIndexPath animationOptions:kASCollectionViewAnimationNone]; - } completion:nil]; - } else { - [super moveItemAtIndexPath:indexPath toIndexPath:newIndexPath]; - } -} - -- (BOOL)beginInteractiveMovementForItemAtIndexPath:(NSIndexPath *)indexPath { - BOOL success = [super beginInteractiveMovementForItemAtIndexPath:indexPath]; - _reordering = success; - return success; -} - -- (void)endInteractiveMovement { - _reordering = NO; - [super endInteractiveMovement]; -} - -- (void)cancelInteractiveMovement { - _reordering = NO; - [super cancelInteractiveMovement]; -} - -#pragma mark - -#pragma mark Intercepted selectors. - -- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView -{ - if (_superIsPendingDataLoad) { - [_rangeController setNeedsUpdate]; - [self _scheduleCheckForBatchFetchingForNumberOfChanges:1]; - _superIsPendingDataLoad = NO; - } - return _dataController.visibleMap.numberOfSections; -} - -- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section -{ - return [_dataController.visibleMap numberOfItemsInSection:section]; -} - -- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout - sizeForItemAtIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertMainThread(); - ASCollectionElement *e = [_dataController.visibleMap elementForItemAtIndexPath:indexPath]; - return e ? [self sizeForElement:e] : ASFlowLayoutDefault(layout, itemSize, CGSizeZero); -} - -- (CGSize)collectionView:(UICollectionView *)cv layout:(UICollectionViewLayout *)l - referenceSizeForHeaderInSection:(NSInteger)section -{ - ASDisplayNodeAssertMainThread(); - ASElementMap *map = _dataController.visibleMap; - ASCollectionElement *e = [map supplementaryElementOfKind:UICollectionElementKindSectionHeader - atIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]]; - return e ? [self sizeForElement:e] : ASFlowLayoutDefault(l, headerReferenceSize, CGSizeZero); -} - -- (CGSize)collectionView:(UICollectionView *)cv layout:(UICollectionViewLayout *)l - referenceSizeForFooterInSection:(NSInteger)section -{ - ASDisplayNodeAssertMainThread(); - ASElementMap *map = _dataController.visibleMap; - ASCollectionElement *e = [map supplementaryElementOfKind:UICollectionElementKindSectionFooter - atIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]]; - return e ? [self sizeForElement:e] : ASFlowLayoutDefault(l, footerReferenceSize, CGSizeZero); -} - -// For the methods that call delegateIndexPathForSection:withSelector:, translate the section from -// visibleMap to pendingMap. If the section no longer exists, or the delegate doesn't implement -// the selector, we will return NSNotFound (and then use the ASFlowLayoutDefault). -- (NSInteger)delegateIndexForSection:(NSInteger)section withSelector:(SEL)selector -{ - if ([_asyncDelegate respondsToSelector:selector]) { - return [_dataController.pendingMap convertSection:section fromMap:_dataController.visibleMap]; - } else { - return NSNotFound; - } -} - -- (UIEdgeInsets)collectionView:(UICollectionView *)cv layout:(UICollectionViewLayout *)l - insetForSectionAtIndex:(NSInteger)section -{ - section = [self delegateIndexForSection:section withSelector:_cmd]; - if (section != NSNotFound) { - return [(id)_asyncDelegate collectionView:cv layout:l insetForSectionAtIndex:section]; - } - return ASFlowLayoutDefault(l, sectionInset, UIEdgeInsetsZero); -} - -- (CGFloat)collectionView:(UICollectionView *)cv layout:(UICollectionViewLayout *)l - minimumInteritemSpacingForSectionAtIndex:(NSInteger)section -{ - section = [self delegateIndexForSection:section withSelector:_cmd]; - if (section != NSNotFound) { - return [(id)_asyncDelegate collectionView:cv layout:l - minimumInteritemSpacingForSectionAtIndex:section]; - } - return ASFlowLayoutDefault(l, minimumInteritemSpacing, 10.0); // Default is documented as 10.0 -} - -- (CGFloat)collectionView:(UICollectionView *)cv layout:(UICollectionViewLayout *)l - minimumLineSpacingForSectionAtIndex:(NSInteger)section -{ - section = [self delegateIndexForSection:section withSelector:_cmd]; - if (section != NSNotFound) { - return [(id)_asyncDelegate collectionView:cv layout:l - minimumLineSpacingForSectionAtIndex:section]; - } - return ASFlowLayoutDefault(l, minimumLineSpacing, 10.0); // Default is documented as 10.0 -} - -- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - if ([_registeredSupplementaryKinds containsObject:kind] == NO) { - [self registerSupplementaryNodeOfKind:kind]; - } - - UICollectionReusableView *view = nil; - ASCollectionElement *element = [_dataController.visibleMap supplementaryElementOfKind:kind atIndexPath:indexPath]; - ASCellNode *node = element.node; - ASWrapperCellNode *wrapperNode = (node.shouldUseUIKitCell ? (ASWrapperCellNode *)node : nil); - BOOL shouldDequeueExternally = _asyncDataSourceFlags.interopAlwaysDequeue || (_asyncDataSourceFlags.interopViewForSupplementaryElement && wrapperNode); - - if (wrapperNode.viewForSupplementaryBlock) { - view = wrapperNode.viewForSupplementaryBlock(wrapperNode); - } else if (shouldDequeueExternally) { - // This codepath is used for both IGListKit mode, and app-level UICollectionView interop. - view = [(id)_asyncDataSource collectionView:collectionView viewForSupplementaryElementOfKind:kind atIndexPath:indexPath]; - } else { - ASDisplayNodeAssert(node != nil, @"Supplementary node should exist. Kind = %@, indexPath = %@, collectionDataSource = %@", kind, indexPath, self); - view = [self dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:kReuseIdentifier forIndexPath:indexPath]; - } - - if (_ASCollectionReusableView *reusableView = ASDynamicCastStrict(view, _ASCollectionReusableView)) { - reusableView.element = element; - } - - if (node) { - [_rangeController configureContentView:view forCellNode:node]; - } - - return view; -} - -- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath -{ - UICollectionViewCell *cell = nil; - ASCollectionElement *element = [_dataController.visibleMap elementForItemAtIndexPath:indexPath]; - ASCellNode *node = element.node; - ASWrapperCellNode *wrapperNode = (node.shouldUseUIKitCell ? (ASWrapperCellNode *)node : nil); - BOOL shouldDequeueExternally = _asyncDataSourceFlags.interopAlwaysDequeue || (_asyncDataSourceFlags.interop && wrapperNode); - - if (wrapperNode.cellForItemBlock) { - cell = wrapperNode.cellForItemBlock(wrapperNode); - } else if (shouldDequeueExternally) { - cell = [(id)_asyncDataSource collectionView:collectionView cellForItemAtIndexPath:indexPath]; - } else { - cell = [self dequeueReusableCellWithReuseIdentifier:kReuseIdentifier forIndexPath:indexPath]; - } - - ASDisplayNodeAssert(element != nil, @"Element should exist. indexPath = %@, collectionDataSource = %@", indexPath, self); - ASDisplayNodeAssert(cell != nil, @"UICollectionViewCell must not be nil. indexPath = %@, collectionDataSource = %@", indexPath, self); - - if (_ASCollectionViewCell *asCell = ASDynamicCastStrict(cell, _ASCollectionViewCell)) { - asCell.element = element; - [_rangeController configureContentView:cell.contentView forCellNode:node]; - } - - return cell; -} - -- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)rawCell forItemAtIndexPath:(NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.interopWillDisplayCell) { - ASCellNode *node = [self nodeForItemAtIndexPath:indexPath]; - if (node.shouldUseUIKitCell) { - [(id )_asyncDelegate collectionView:collectionView willDisplayCell:rawCell forItemAtIndexPath:indexPath]; - } - } - - _ASCollectionViewCell *cell = ASDynamicCastStrict(rawCell, _ASCollectionViewCell); - if (cell == nil) { - [_rangeController setNeedsUpdate]; - return; - } - - ASCollectionElement *element = cell.element; - if (element) { - ASDisplayNodeAssertTrue([_dataController.visibleMap elementForItemAtIndexPath:indexPath] == element); - [_visibleElements addObject:element]; - } else { - ASDisplayNodeAssert(NO, @"Unexpected nil element for willDisplayCell: %@, %@, %@", rawCell, self, indexPath); - return; - } - - ASCellNode *cellNode = element.node; - cellNode.scrollView = collectionView; - - // Update the selected background view in collectionView:willDisplayCell:forItemAtIndexPath: otherwise it could be too - // early e.g. if the selectedBackgroundView was set in didLoad() - cell.selectedBackgroundView = cellNode.selectedBackgroundView; - cell.backgroundView = cellNode.backgroundView; - - // Under iOS 10+, cells may be removed/re-added to the collection view without - // receiving prepareForReuse/applyLayoutAttributes, as an optimization for e.g. - // if the user is scrolling back and forth across a small set of items. - // In this case, we have to fetch the layout attributes manually. - // This may be possible under iOS < 10 but it has not been observed yet. - if (cell.layoutAttributes == nil) { - cell.layoutAttributes = [collectionView layoutAttributesForItemAtIndexPath:indexPath]; - } - - ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with cell that will be displayed not to be nil. indexPath: %@", indexPath); - - if (_asyncDelegateFlags.collectionNodeWillDisplayItem && self.collectionNode != nil) { - [_asyncDelegate collectionNode:self.collectionNode willDisplayItemWithNode:cellNode]; - } else if (_asyncDelegateFlags.collectionViewWillDisplayNodeForItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate collectionView:self willDisplayNode:cellNode forItemAtIndexPath:indexPath]; - } else if (_asyncDelegateFlags.collectionViewWillDisplayNodeForItemDeprecated) { - [_asyncDelegate collectionView:self willDisplayNodeForItemAtIndexPath:indexPath]; - } -#pragma clang diagnostic pop - - [_rangeController setNeedsUpdate]; - - if ([cell consumesCellNodeVisibilityEvents]) { - [_cellsForVisibilityUpdates addObject:cell]; - } -} - -- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)rawCell forItemAtIndexPath:(NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.interopDidEndDisplayingCell) { - ASCellNode *node = [self nodeForItemAtIndexPath:indexPath]; - if (node.shouldUseUIKitCell) { - [(id )_asyncDelegate collectionView:collectionView didEndDisplayingCell:rawCell forItemAtIndexPath:indexPath]; - } - } - - _ASCollectionViewCell *cell = ASDynamicCastStrict(rawCell, _ASCollectionViewCell); - if (cell == nil) { - [_rangeController setNeedsUpdate]; - return; - } - - // Retrieve the element from cell instead of visible map because at this point visible map could have been updated and no longer holds the element. - ASCollectionElement *element = cell.element; - if (element) { - [_visibleElements removeObject:element]; - } else { - ASDisplayNodeAssert(NO, @"Unexpected nil element for didEndDisplayingCell: %@, %@, %@", rawCell, self, indexPath); - return; - } - - ASCellNode *cellNode = element.node; - - if (_asyncDelegateFlags.collectionNodeDidEndDisplayingItem) { - if (ASCollectionNode *collectionNode = self.collectionNode) { - [_asyncDelegate collectionNode:collectionNode didEndDisplayingItemWithNode:cellNode]; - } - } else if (_asyncDelegateFlags.collectionViewDidEndDisplayingNodeForItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate collectionView:self didEndDisplayingNode:cellNode forItemAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } - - [_rangeController setNeedsUpdate]; - - [_cellsForVisibilityUpdates removeObject:cell]; - - cellNode.scrollView = nil; - cell.layoutAttributes = nil; -} - -- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)rawView forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.interopWillDisplaySupplementaryView) { - ASCellNode *node = [self supplementaryNodeForElementKind:elementKind atIndexPath:indexPath]; - if (node.shouldUseUIKitCell) { - [(id )_asyncDelegate collectionView:collectionView willDisplaySupplementaryView:rawView forElementKind:elementKind atIndexPath:indexPath]; - } - } - - _ASCollectionReusableView *view = ASDynamicCastStrict(rawView, _ASCollectionReusableView); - if (view == nil) { - return; - } - - ASCollectionElement *element = view.element; - if (element) { - ASDisplayNodeAssertTrue([_dataController.visibleMap supplementaryElementOfKind:elementKind atIndexPath:indexPath] == view.element); - [_visibleElements addObject:element]; - } else { - ASDisplayNodeAssert(NO, @"Unexpected nil element for willDisplaySupplementaryView: %@, %@, %@", rawView, self, indexPath); - return; - } - - // Under iOS 10+, cells may be removed/re-added to the collection view without - // receiving prepareForReuse/applyLayoutAttributes, as an optimization for e.g. - // if the user is scrolling back and forth across a small set of items. - // In this case, we have to fetch the layout attributes manually. - // This may be possible under iOS < 10 but it has not been observed yet. - if (view.layoutAttributes == nil) { - view.layoutAttributes = [collectionView layoutAttributesForSupplementaryElementOfKind:elementKind atIndexPath:indexPath]; - } - - if (_asyncDelegateFlags.collectionNodeWillDisplaySupplementaryElement) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0); - ASCellNode *node = element.node; - ASDisplayNodeAssert([node.supplementaryElementKind isEqualToString:elementKind], @"Expected node for supplementary element to have kind '%@', got '%@'.", elementKind, node.supplementaryElementKind); - [_asyncDelegate collectionNode:collectionNode willDisplaySupplementaryElementWithNode:node]; - } -} - -- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)rawView forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.interopdidEndDisplayingSupplementaryView) { - ASCellNode *node = [self supplementaryNodeForElementKind:elementKind atIndexPath:indexPath]; - if (node.shouldUseUIKitCell) { - [(id )_asyncDelegate collectionView:collectionView didEndDisplayingSupplementaryView:rawView forElementOfKind:elementKind atIndexPath:indexPath]; - } - } - - _ASCollectionReusableView *view = ASDynamicCastStrict(rawView, _ASCollectionReusableView); - if (view == nil) { - return; - } - - // Retrieve the element from cell instead of visible map because at this point visible map could have been updated and no longer holds the element. - ASCollectionElement *element = view.element; - if (element) { - [_visibleElements removeObject:element]; - } else { - ASDisplayNodeAssert(NO, @"Unexpected nil element for didEndDisplayingSupplementaryView: %@, %@, %@", rawView, self, indexPath); - return; - } - - if (_asyncDelegateFlags.collectionNodeDidEndDisplayingSupplementaryElement) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0); - ASCellNode *node = element.node; - ASDisplayNodeAssert([node.supplementaryElementKind isEqualToString:elementKind], @"Expected node for supplementary element to have kind '%@', got '%@'.", elementKind, node.supplementaryElementKind); - [_asyncDelegate collectionNode:collectionNode didEndDisplayingSupplementaryElementWithNode:node]; - } -} - -- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.collectionNodeShouldSelectItem) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO); - indexPath = [self convertIndexPathToCollectionNode:indexPath]; - if (indexPath != nil) { - return [_asyncDelegate collectionNode:collectionNode shouldSelectItemAtIndexPath:indexPath]; - } - } else if (_asyncDelegateFlags.collectionViewShouldSelectItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate collectionView:self shouldSelectItemAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } - return YES; -} - -- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(nonnull NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.collectionNodeDidSelectItem) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0); - indexPath = [self convertIndexPathToCollectionNode:indexPath]; - if (indexPath != nil) { - [_asyncDelegate collectionNode:collectionNode didSelectItemAtIndexPath:indexPath]; - } - } else if (_asyncDelegateFlags.collectionViewDidSelectItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate collectionView:self didSelectItemAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } -} - -- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.collectionNodeShouldDeselectItem) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO); - indexPath = [self convertIndexPathToCollectionNode:indexPath]; - if (indexPath != nil) { - return [_asyncDelegate collectionNode:collectionNode shouldDeselectItemAtIndexPath:indexPath]; - } - } else if (_asyncDelegateFlags.collectionViewShouldDeselectItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate collectionView:self shouldDeselectItemAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } - return YES; -} - -- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(nonnull NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.collectionNodeDidDeselectItem) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0); - indexPath = [self convertIndexPathToCollectionNode:indexPath]; - if (indexPath != nil) { - [_asyncDelegate collectionNode:collectionNode didDeselectItemAtIndexPath:indexPath]; - } - } else if (_asyncDelegateFlags.collectionViewDidDeselectItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate collectionView:self didDeselectItemAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } -} - -- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.collectionNodeShouldHighlightItem) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO); - indexPath = [self convertIndexPathToCollectionNode:indexPath]; - if (indexPath != nil) { - return [_asyncDelegate collectionNode:collectionNode shouldHighlightItemAtIndexPath:indexPath]; - } else { - return YES; - } - } else if (_asyncDelegateFlags.collectionViewShouldHighlightItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate collectionView:self shouldHighlightItemAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } - return YES; -} - -- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(nonnull NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.collectionNodeDidHighlightItem) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0); - indexPath = [self convertIndexPathToCollectionNode:indexPath]; - if (indexPath != nil) { - [_asyncDelegate collectionNode:collectionNode didHighlightItemAtIndexPath:indexPath]; - } - } else if (_asyncDelegateFlags.collectionViewDidHighlightItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate collectionView:self didHighlightItemAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } -} - -- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(nonnull NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.collectionNodeDidUnhighlightItem) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0); - indexPath = [self convertIndexPathToCollectionNode:indexPath]; - if (indexPath != nil) { - [_asyncDelegate collectionNode:collectionNode didUnhighlightItemAtIndexPath:indexPath]; - } - } else if (_asyncDelegateFlags.collectionViewDidUnhighlightItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate collectionView:self didUnhighlightItemAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } -} - -- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(nonnull NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.collectionNodeShouldShowMenuForItem) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO); - indexPath = [self convertIndexPathToCollectionNode:indexPath]; - if (indexPath != nil) { - return [_asyncDelegate collectionNode:collectionNode shouldShowMenuForItemAtIndexPath:indexPath]; - } - } else if (_asyncDelegateFlags.collectionViewShouldShowMenuForItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate collectionView:self shouldShowMenuForItemAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } - return NO; -} - -- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(nonnull SEL)action forItemAtIndexPath:(nonnull NSIndexPath *)indexPath withSender:(nullable id)sender -{ - if (_asyncDelegateFlags.collectionNodeCanPerformActionForItem) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO); - indexPath = [self convertIndexPathToCollectionNode:indexPath]; - if (indexPath != nil) { - return [_asyncDelegate collectionNode:collectionNode canPerformAction:action forItemAtIndexPath:indexPath sender:sender]; - } - } else if (_asyncDelegateFlags.collectionViewCanPerformActionForItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate collectionView:self canPerformAction:action forItemAtIndexPath:indexPath withSender:sender]; -#pragma clang diagnostic pop - } - return NO; -} - -- (void)collectionView:(UICollectionView *)collectionView performAction:(nonnull SEL)action forItemAtIndexPath:(nonnull NSIndexPath *)indexPath withSender:(nullable id)sender -{ - if (_asyncDelegateFlags.collectionNodePerformActionForItem) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0); - indexPath = [self convertIndexPathToCollectionNode:indexPath]; - if (indexPath != nil) { - [_asyncDelegate collectionNode:collectionNode performAction:action forItemAtIndexPath:indexPath sender:sender]; - } - } else if (_asyncDelegateFlags.collectionViewPerformActionForItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate collectionView:self performAction:action forItemAtIndexPath:indexPath withSender:sender]; -#pragma clang diagnostic pop - } -} - -- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath -{ - // Mimic UIKit's gating logic. - // If the data source doesn't support moving, then all bets are off. - if (!_asyncDataSourceFlags.collectionNodeMoveItem) { - return NO; - } - - // Currently we do not support interactive moves when using async layout. The reason is, we do not have a mechanism - // to propagate the "presentation data" element map (containing the speculative in-progress moves) to the layout delegate, - // and this can cause exceptions to be thrown from UICV. For example, if you drag an item out of a section, - // the element map will still contain N items in that section, even though there's only N-1 shown, and UICV will - // throw an exception that you specified an element that doesn't exist. - // - // In iOS >= 11, this is made much easier by the UIDataSourceTranslating API. In previous versions of iOS our best bet - // would be to capture the invalidation contexts that are sent during interactive moves and make our own data source translator. - if ([self.collectionViewLayout isKindOfClass:[ASCollectionLayout class]]) { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - as_log_debug(ASCollectionLog(), "Collection node item interactive movement is not supported when using a layout delegate. This message will only be logged once. Node: %@", ASObjectDescriptionMakeTiny(self)); - }); - return NO; - } - - // If the data source implements canMoveItem, let them decide. - if (_asyncDataSourceFlags.collectionNodeCanMoveItem) { - if (ASCellNode *cellNode = [self nodeForItemAtIndexPath:indexPath]) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO); - return [_asyncDataSource collectionNode:collectionNode canMoveItemWithNode:cellNode]; - } - } - - // Otherwise allow the move for all items. - return YES; -} - -- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath -{ - ASDisplayNodeAssert(_asyncDataSourceFlags.collectionNodeMoveItem, @"Should not allow interactive collection item movement if data source does not support it."); - - // Inform the data source first, in case they call nodeForItemAtIndexPath:. - // We want to make sure we return them the node for the item they have in mind. - if (ASCollectionNode *collectionNode = self.collectionNode) { - [_asyncDataSource collectionNode:collectionNode moveItemAtIndexPath:sourceIndexPath toIndexPath:destinationIndexPath]; - } - - // Now we update our data controller's store. - // Get up to date - [self waitUntilAllUpdatesAreCommitted]; - // Set our flag to suppress informing super about the change. - ASDisplayNodeAssertFalse(_updatingInResponseToInteractiveMove); - _updatingInResponseToInteractiveMove = YES; - // Submit the move - [self moveItemAtIndexPath:sourceIndexPath toIndexPath:destinationIndexPath]; - // Wait for it to finish – should be fast! - [self waitUntilAllUpdatesAreCommitted]; - // Clear the flag - _updatingInResponseToInteractiveMove = NO; -} - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView -{ - ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController]; - if (ASInterfaceStateIncludesVisible(interfaceState)) { - [self _checkForBatchFetching]; - } - for (_ASCollectionViewCell *cell in _cellsForVisibilityUpdates) { - // _cellsForVisibilityUpdates only includes cells for ASCellNode subclasses with overrides of the visibility method. - [cell cellNodeVisibilityEvent:ASCellNodeVisibilityEventVisibleRectChanged inScrollView:scrollView]; - } - if (_asyncDelegateFlags.scrollViewDidScroll) { - [_asyncDelegate scrollViewDidScroll:scrollView]; - } -} - -- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset -{ - CGPoint contentOffset = scrollView.contentOffset; - _deceleratingVelocity = CGPointMake( - contentOffset.x - ((targetContentOffset != NULL) ? targetContentOffset->x : 0), - contentOffset.y - ((targetContentOffset != NULL) ? targetContentOffset->y : 0) - ); - - if (targetContentOffset != NULL) { - ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist"); - [self _beginBatchFetchingIfNeededWithContentOffset:*targetContentOffset velocity:velocity]; - } - - if (_asyncDelegateFlags.scrollViewWillEndDragging) { - [_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:(targetContentOffset ? : &contentOffset)]; - } -} - -- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView -{ - _deceleratingVelocity = CGPointZero; - - if (_asyncDelegateFlags.scrollViewDidEndDecelerating) { - [_asyncDelegate scrollViewDidEndDecelerating:scrollView]; - } -} - -- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView -{ - // If a scroll happens the current range mode needs to go to full - _rangeController.contentHasBeenScrolled = YES; - [_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull]; - - for (_ASCollectionViewCell *cell in _cellsForVisibilityUpdates) { - [cell cellNodeVisibilityEvent:ASCellNodeVisibilityEventWillBeginDragging inScrollView:scrollView]; - } - if (_asyncDelegateFlags.scrollViewWillBeginDragging) { - [_asyncDelegate scrollViewWillBeginDragging:scrollView]; - } -} - -- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate -{ - for (_ASCollectionViewCell *cell in _cellsForVisibilityUpdates) { - [cell cellNodeVisibilityEvent:ASCellNodeVisibilityEventDidEndDragging inScrollView:scrollView]; - } - if (_asyncDelegateFlags.scrollViewDidEndDragging) { - [_asyncDelegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate]; - } -} - -#pragma mark - Scroll Direction. - -- (BOOL)inverted -{ - return _inverted; -} - -- (void)setInverted:(BOOL)inverted -{ - _inverted = inverted; -} - -- (void)setLeadingScreensForBatching:(CGFloat)leadingScreensForBatching -{ - if (_leadingScreensForBatching != leadingScreensForBatching) { - _leadingScreensForBatching = leadingScreensForBatching; - // Push this to the next runloop to be sure the scroll view has the right content size - dispatch_async(dispatch_get_main_queue(), ^{ - [self _checkForBatchFetching]; - }); - } -} - -- (CGFloat)leadingScreensForBatching -{ - return _leadingScreensForBatching; -} - -- (ASScrollDirection)scrollDirection -{ - CGPoint scrollVelocity; - if (self.isTracking) { - scrollVelocity = [self.panGestureRecognizer velocityInView:self.superview]; - } else { - scrollVelocity = _deceleratingVelocity; - } - - ASScrollDirection scrollDirection = [self _scrollDirectionForVelocity:scrollVelocity]; - return ASScrollDirectionApplyTransform(scrollDirection, self.transform); -} - -- (ASScrollDirection)_scrollDirectionForVelocity:(CGPoint)scrollVelocity -{ - ASScrollDirection direction = ASScrollDirectionNone; - ASScrollDirection scrollableDirections = [self scrollableDirections]; - - if (ASScrollDirectionContainsHorizontalDirection(scrollableDirections)) { // Can scroll horizontally. - if (scrollVelocity.x < 0.0) { - direction |= ASScrollDirectionRight; - } else if (scrollVelocity.x > 0.0) { - direction |= ASScrollDirectionLeft; - } - } - if (ASScrollDirectionContainsVerticalDirection(scrollableDirections)) { // Can scroll vertically. - if (scrollVelocity.y < 0.0) { - direction |= ASScrollDirectionDown; - } else if (scrollVelocity.y > 0.0) { - direction |= ASScrollDirectionUp; - } - } - - return direction; -} - -- (ASScrollDirection)scrollableDirections -{ - ASDisplayNodeAssertNotNil(self.layoutInspector, @"Layout inspector should be assigned."); - return [self.layoutInspector scrollableDirections]; -} - -- (void)layoutSubviews -{ - if (_cellsForLayoutUpdates.count > 0) { - NSArray *nodes = [_cellsForLayoutUpdates allObjects]; - [_cellsForLayoutUpdates removeAllObjects]; - - NSMutableArray *nodesSizeChanged = [[NSMutableArray alloc] init]; - - [_dataController relayoutNodes:nodes nodesSizeChanged:nodesSizeChanged]; - [self nodesDidRelayout:nodesSizeChanged]; - } - - // Flush any pending invalidation action if needed. - ASCollectionViewInvalidationStyle invalidationStyle = _nextLayoutInvalidationStyle; - _nextLayoutInvalidationStyle = ASCollectionViewInvalidationStyleNone; - switch (invalidationStyle) { - case ASCollectionViewInvalidationStyleWithAnimation: - if (0 == _superBatchUpdateCount) { - if (ASCellLayoutModeIncludes(ASCellLayoutModeAlwaysReloadData)) { - [self _superReloadData:nil completion:nil]; - } else { - [self _superPerformBatchUpdates:nil completion:nil]; - } - } - break; - case ASCollectionViewInvalidationStyleWithoutAnimation: - [self.collectionViewLayout invalidateLayout]; - break; - default: - break; - } - - // To ensure _maxSizeForNodesConstrainedSize is up-to-date for every usage, this call to super must be done last - [super layoutSubviews]; - - if (_zeroContentInsets) { - self.contentInset = UIEdgeInsetsZero; - } - - // Update range controller immediately if possible & needed. - // Calling -updateIfNeeded in here with self.window == nil (early in the collection view's life) - // may cause UICollectionView data related crashes. We'll update in -didMoveToWindow anyway. - if (self.window != nil) { - [_rangeController updateIfNeeded]; - } -} - - -#pragma mark - Batch Fetching - -- (ASBatchContext *)batchContext -{ - return _batchContext; -} - -- (BOOL)canBatchFetch -{ - // if the delegate does not respond to this method, there is no point in starting to fetch - BOOL canFetch = _asyncDelegateFlags.collectionNodeWillBeginBatchFetch || _asyncDelegateFlags.collectionViewWillBeginBatchFetch; - if (canFetch && _asyncDelegateFlags.shouldBatchFetchForCollectionNode) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO); - return [_asyncDelegate shouldBatchFetchForCollectionNode:collectionNode]; - } else if (canFetch && _asyncDelegateFlags.shouldBatchFetchForCollectionView) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate shouldBatchFetchForCollectionView:self]; -#pragma clang diagnostic pop - } else { - return canFetch; - } -} - -- (id)batchFetchingDelegate{ - return self.collectionNode.batchFetchingDelegate; -} - -- (void)_scheduleCheckForBatchFetchingForNumberOfChanges:(NSUInteger)changes -{ - // Prevent fetching will continually trigger in a loop after reaching end of content and no new content was provided - if (changes == 0 && _hasEverCheckedForBatchFetchingDueToUpdate) { - return; - } - _hasEverCheckedForBatchFetchingDueToUpdate = YES; - - // Push this to the next runloop to be sure the scroll view has the right content size - dispatch_async(dispatch_get_main_queue(), ^{ - [self _checkForBatchFetching]; - }); -} - -- (void)_checkForBatchFetching -{ - // Dragging will be handled in scrollViewWillEndDragging:withVelocity:targetContentOffset: - if (self.isDragging || self.isTracking) { - return; - } - - [self _beginBatchFetchingIfNeededWithContentOffset:self.contentOffset velocity:CGPointZero]; -} - -- (void)_beginBatchFetchingIfNeededWithContentOffset:(CGPoint)contentOffset velocity:(CGPoint)velocity -{ - if (ASDisplayShouldFetchBatchForScrollView(self, self.scrollDirection, self.scrollableDirections, contentOffset, velocity)) { - [self _beginBatchFetching]; - } -} - -- (void)_beginBatchFetching -{ - as_activity_create_for_scope("Batch fetch for collection node"); - [_batchContext beginBatchFetching]; - if (_asyncDelegateFlags.collectionNodeWillBeginBatchFetch) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0); - as_log_debug(ASCollectionLog(), "Beginning batch fetch for %@ with context %@", collectionNode, _batchContext); - [_asyncDelegate collectionNode:collectionNode willBeginBatchFetchWithContext:_batchContext]; - }); - } else if (_asyncDelegateFlags.collectionViewWillBeginBatchFetch) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate collectionView:self willBeginBatchFetchWithContext:_batchContext]; -#pragma clang diagnostic pop - }); - } -} - -#pragma mark - ASDataControllerSource - -- (BOOL)dataController:(ASDataController *)dataController shouldEagerlyLayoutNode:(ASCellNode *)node -{ - NSAssert(!ASCellLayoutModeIncludes(ASCellLayoutModeAlwaysLazy), - @"ASCellLayoutModeAlwaysLazy flag is no longer supported"); - return !node.shouldUseUIKitCell; -} - -- (BOOL)dataController:(ASDataController *)dataController shouldSynchronouslyProcessChangeSet:(_ASHierarchyChangeSet *)changeSet -{ - // If we have AlwaysSync set, block and donate main priority. - if (ASCellLayoutModeIncludes(ASCellLayoutModeAlwaysSync)) { - return YES; - } - // Prioritize AlwaysAsync over the remaining heuristics for the Default mode. - if (ASCellLayoutModeIncludes(ASCellLayoutModeAlwaysAsync)) { - return NO; - } - // Reload data is expensive, don't block main while doing so. - if (changeSet.includesReloadData) { - return NO; - } - // If we have very few ASCellNodes (besides UIKit passthrough ones), match UIKit by blocking. - if (changeSet.countForAsyncLayout < 2) { - return YES; - } - CGSize contentSize = self.contentSize; - CGSize boundsSize = self.bounds.size; - if (contentSize.height <= boundsSize.height && contentSize.width <= boundsSize.width) { - return YES; - } - return NO; // ASCellLayoutModeNone -} - -- (BOOL)dataControllerShouldSerializeNodeCreation:(ASDataController *)dataController -{ - return ASCellLayoutModeIncludes(ASCellLayoutModeSerializeNodeCreation); -} - -- (id)dataController:(ASDataController *)dataController nodeModelForItemAtIndexPath:(NSIndexPath *)indexPath -{ - if (!_asyncDataSourceFlags.nodeModelForItem) { - return nil; - } - - GET_COLLECTIONNODE_OR_RETURN(collectionNode, nil); - return [_asyncDataSource collectionNode:collectionNode nodeModelForItemAtIndexPath:indexPath]; -} - -- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath shouldAsyncLayout:(BOOL *)shouldAsyncLayout -{ - ASDisplayNodeAssertMainThread(); - ASCellNodeBlock block = nil; - ASCellNode *cell = nil; - - if (_asyncDataSourceFlags.collectionNodeNodeBlockForItem) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, ^{ return [[ASCellNode alloc] init]; }); - block = [_asyncDataSource collectionNode:collectionNode nodeBlockForItemAtIndexPath:indexPath]; - } - if (!block && !cell && _asyncDataSourceFlags.collectionNodeNodeForItem) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, ^{ return [[ASCellNode alloc] init]; }); - cell = [_asyncDataSource collectionNode:collectionNode nodeForItemAtIndexPath:indexPath]; - } -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - if (!block && !cell && _asyncDataSourceFlags.collectionViewNodeBlockForItem) { - block = [_asyncDataSource collectionView:self nodeBlockForItemAtIndexPath:indexPath]; - } - if (!block && !cell && _asyncDataSourceFlags.collectionViewNodeForItem) { - cell = [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath]; - } -#pragma clang diagnostic pop - - if (block == nil) { - if (cell == nil || ASDynamicCast(cell, ASCellNode) == nil) { - // In this case, either the client is expecting a UIKit passthrough cell to be created automatically, - // or it is an error. - if (_asyncDataSourceFlags.interop) { - cell = [[ASWrapperCellNode alloc] init]; - } else { - ASDisplayNodeFailAssert(@"ASCollection could not get a node block for item at index path %@: %@, %@. If you are trying to display a UICollectionViewCell, make sure your dataSource conforms to the protocol!", indexPath, cell, block); - cell = [[ASCellNode alloc] init]; - } - } - - // This condition is intended to run for either cells received from the datasource, or created just above. - if (cell.shouldUseUIKitCell) { - *shouldAsyncLayout = NO; - } - } - - // Wrap the node block - BOOL disableRangeController = ASCellLayoutModeIncludes(ASCellLayoutModeDisableRangeController); - __weak __typeof__(self) weakSelf = self; - return ^{ - __typeof__(self) strongSelf = weakSelf; - ASCellNode *node = (block ? block() : cell); - ASDisplayNodeAssert([node isKindOfClass:[ASCellNode class]], @"ASCollectionNode provided a non-ASCellNode! %@, %@", node, strongSelf); - - if (!disableRangeController) { - [node enterHierarchyState:ASHierarchyStateRangeManaged]; - } - if (node.interactionDelegate == nil) { - node.interactionDelegate = strongSelf; - } - if (strongSelf.inverted) { - node.transform = CATransform3DMakeScale(1, -1, 1); - } - return node; - }; -} - -- (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section -{ - if (_asyncDataSourceFlags.collectionNodeNumberOfItemsInSection) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, 0); - return [_asyncDataSource collectionNode:collectionNode numberOfItemsInSection:section]; - } else if (_asyncDataSourceFlags.collectionViewNumberOfItemsInSection) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDataSource collectionView:self numberOfItemsInSection:section]; -#pragma clang diagnostic pop - } else { - return 0; - } -} - -- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController { - if (_asyncDataSourceFlags.numberOfSectionsInCollectionNode) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, 0); - return [_asyncDataSource numberOfSectionsInCollectionNode:collectionNode]; - } else if (_asyncDataSourceFlags.numberOfSectionsInCollectionView) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDataSource numberOfSectionsInCollectionView:self]; -#pragma clang diagnostic pop - } else { - return 1; - } -} - -- (BOOL)dataController:(ASDataController *)dataController presentedSizeForElement:(ASCollectionElement *)element matchesSize:(CGSize)size -{ - NSIndexPath *indexPath = [self indexPathForNode:element.node]; - if (indexPath == nil) { - ASDisplayNodeFailAssert(@"Data controller should not ask for presented size for element that is not presented."); - return YES; - } - - UICollectionViewLayoutAttributes *attributes; - if (element.supplementaryElementKind == nil) { - attributes = [self layoutAttributesForItemAtIndexPath:indexPath]; - } else { - attributes = [self layoutAttributesForSupplementaryElementOfKind:element.supplementaryElementKind atIndexPath:indexPath]; - } - return CGSizeEqualToSizeWithIn(attributes.size, size, FLT_EPSILON); -} - -#pragma mark - ASDataControllerSource optional methods - -- (ASCellNodeBlock)dataController:(ASDataController *)dataController supplementaryNodeBlockOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath shouldAsyncLayout:(BOOL *)shouldAsyncLayout -{ - ASDisplayNodeAssertMainThread(); - ASCellNodeBlock block = nil; - ASCellNode *cell = nil; - if (_asyncDataSourceFlags.collectionNodeNodeBlockForSupplementaryElement) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, ^{ return [[ASCellNode alloc] init]; }); - block = [_asyncDataSource collectionNode:collectionNode nodeBlockForSupplementaryElementOfKind:kind atIndexPath:indexPath]; - } - if (!block && !cell && _asyncDataSourceFlags.collectionNodeNodeForSupplementaryElement) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, ^{ return [[ASCellNode alloc] init]; }); - cell = [_asyncDataSource collectionNode:collectionNode nodeForSupplementaryElementOfKind:kind atIndexPath:indexPath]; - } - if (!block && !cell && _asyncDataSourceFlags.collectionViewNodeForSupplementaryElement) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - cell = [_asyncDataSource collectionView:self nodeForSupplementaryElementOfKind:kind atIndexPath:indexPath]; -#pragma clang diagnostic pop - } - - if (block == nil) { - if (cell == nil || ASDynamicCast(cell, ASCellNode) == nil) { - // In this case, the app code returned nil for the node and the nodeBlock. - // If the UIKit method is implemented, then we should use a passthrough cell. - // Otherwise the CGSizeZero default will cause UIKit to not show it (so this isn't an error like the cellForItem case). - - BOOL useUIKitCell = _asyncDataSourceFlags.interopViewForSupplementaryElement; - if (useUIKitCell) { - cell = [[ASWrapperCellNode alloc] init]; - } else { - cell = [[ASCellNode alloc] init]; - } - } - - // This condition is intended to run for either cells received from the datasource, or created just above. - if (cell.shouldUseUIKitCell) { - *shouldAsyncLayout = NO; - } - - block = ^{ return cell; }; - } - - // Wrap the node block - // BOOL disableRangeController = ASCellLayoutModeIncludes(ASCellLayoutModeDisableRangeController); - __weak __typeof__(self) weakSelf = self; - return ^{ - __typeof__(self) strongSelf = weakSelf; - ASCellNode *node = block(); - ASDisplayNodeAssert([node isKindOfClass:[ASCellNode class]], - @"ASCollectionNode provided a non-ASCellNode! %@, %@", node, strongSelf); - - // TODO: ASRangeController doesn't currently support managing interfaceState for supplementary nodes. - // For now, we allow the standard ASInterfaceStateInHierarchy behavior by ensuring we do not inform - // the node that it should expect external management of interfaceState. - /* - if (!disableRangeController) { - [node enterHierarchyState:ASHierarchyStateRangeManaged]; - } - */ - - if (node.interactionDelegate == nil) { - node.interactionDelegate = strongSelf; - } - if (strongSelf.inverted) { - node.transform = CATransform3DMakeScale(1, -1, 1); - } - return node; - }; -} - -- (NSArray *)dataController:(ASDataController *)dataController supplementaryNodeKindsInSections:(NSIndexSet *)sections -{ - if (_asyncDataSourceFlags.collectionNodeSupplementaryElementKindsInSection) { - const auto kinds = [[NSMutableSet alloc] init]; - GET_COLLECTIONNODE_OR_RETURN(collectionNode, @[]); - [sections enumerateIndexesUsingBlock:^(NSUInteger section, BOOL * _Nonnull stop) { - NSArray *kindsForSection = [_asyncDataSource collectionNode:collectionNode supplementaryElementKindsInSection:section]; - [kinds addObjectsFromArray:kindsForSection]; - }]; - return [kinds allObjects]; - } else { - // TODO: Lock this - return [_registeredSupplementaryKinds allObjects]; - } -} - -- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath -{ - return [self.layoutInspector collectionView:self constrainedSizeForNodeAtIndexPath:indexPath]; -} - -- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - if (_layoutInspectorFlags.constrainedSizeForSupplementaryNodeOfKindAtIndexPath) { - return [self.layoutInspector collectionView:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:indexPath]; - } - - ASDisplayNodeAssert(NO, @"To support supplementary nodes in ASCollectionView, it must have a layoutInspector for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)"); - return ASSizeRangeMake(CGSizeZero, CGSizeZero); -} - -- (NSUInteger)dataController:(ASDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section -{ - if (_asyncDataSource == nil) { - return 0; - } - - if (_layoutInspectorFlags.supplementaryNodesOfKindInSection) { - return [self.layoutInspector collectionView:self supplementaryNodesOfKind:kind inSection:section]; - } - - ASDisplayNodeAssert(NO, @"To support supplementary nodes in ASCollectionView, it must have a layoutInspector for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)"); - return 0; -} - -- (id)dataController:(ASDataController *)dataController contextForSection:(NSInteger)section -{ - ASDisplayNodeAssertMainThread(); - id context = nil; - - if (_asyncDataSourceFlags.collectionNodeContextForSection) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, nil); - context = [_asyncDataSource collectionNode:collectionNode contextForSection:section]; - } - - if (context != nil) { - context.collectionView = self; - } - return context; -} - -#pragma mark - ASRangeControllerDataSource - -- (ASRangeController *)rangeController -{ - return _rangeController; -} - -- (NSHashTable *)visibleElementsForRangeController:(ASRangeController *)rangeController -{ - return ASPointerTableByFlatMapping(_visibleElements, id element, element); -} - -- (ASElementMap *)elementMapForRangeController:(ASRangeController *)rangeController -{ - return _dataController.visibleMap; -} - -- (ASScrollDirection)scrollDirectionForRangeController:(ASRangeController *)rangeController -{ - return self.scrollDirection; -} - -- (ASInterfaceState)interfaceStateForRangeController:(ASRangeController *)rangeController -{ - return ASInterfaceStateForDisplayNode(self.collectionNode, self.window); -} - -- (NSString *)nameForRangeControllerDataSource -{ - return self.asyncDataSource ? NSStringFromClass([self.asyncDataSource class]) : NSStringFromClass([self class]); -} - -#pragma mark - ASRangeControllerDelegate - -- (BOOL)rangeControllerShouldUpdateRanges:(ASRangeController *)rangeController -{ - return !ASCellLayoutModeIncludes(ASCellLayoutModeDisableRangeController); -} - -- (void)rangeController:(ASRangeController *)rangeController updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet updates:(dispatch_block_t)updates -{ - ASDisplayNodeAssertMainThread(); - if (!self.asyncDataSource || _superIsPendingDataLoad || _updatingInResponseToInteractiveMove) { - updates(); - [changeSet executeCompletionHandlerWithFinished:NO]; - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes - } - - //TODO Do we need to notify _layoutFacilitator before reloadData? - for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeDelete]) { - [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:change.indexPaths batched:YES]; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeDelete]) { - [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:change.indexSet batched:YES]; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) { - [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:change.indexSet batched:YES]; - } - - for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeInsert]) { - [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:change.indexPaths batched:YES]; - } - - ASPerformBlockWithoutAnimation(!changeSet.animated, ^{ - as_activity_scope(as_activity_create("Commit collection update", changeSet.rootActivity, OS_ACTIVITY_FLAG_DEFAULT)); - if (changeSet.includesReloadData) { - _superIsPendingDataLoad = YES; - updates(); - [self _superReloadData:nil completion:nil]; - as_log_debug(ASCollectionLog(), "Did reloadData %@", self.collectionNode); - [changeSet executeCompletionHandlerWithFinished:YES]; - } else { - [_layoutFacilitator collectionViewWillPerformBatchUpdates]; - - __block NSUInteger numberOfUpdates = 0; - const auto completion = ^(BOOL finished) { - as_activity_scope(as_activity_create("Handle collection update completion", changeSet.rootActivity, OS_ACTIVITY_FLAG_DEFAULT)); - as_log_verbose(ASCollectionLog(), "Update animation finished %{public}@", self.collectionNode); - // Flush any range changes that happened as part of the update animations ending. - [_rangeController updateIfNeeded]; - [self _scheduleCheckForBatchFetchingForNumberOfChanges:numberOfUpdates]; - [changeSet executeCompletionHandlerWithFinished:finished]; - }; - - BOOL shouldReloadData = ASCellLayoutModeIncludes(ASCellLayoutModeAlwaysReloadData); - // TODO: Consider adding !changeSet.isEmpty as a check to also disable shouldReloadData. - if (ASCellLayoutModeIncludes(ASCellLayoutModeAlwaysBatchUpdateSectionReload) && - [changeSet sectionChangesOfType:_ASHierarchyChangeTypeReload].count > 0) { - shouldReloadData = NO; - } - - if (shouldReloadData) { - // When doing a reloadData, the insert / delete calls are not necessary. - // Calling updates() is enough, as it commits .pendingMap to .visibleMap. - [self _superReloadData:updates completion:completion]; - } else { - [self _superPerformBatchUpdates:^{ - updates(); - - for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeReload]) { - [super reloadItemsAtIndexPaths:change.indexPaths]; - numberOfUpdates++; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeReload]) { - [super reloadSections:change.indexSet]; - numberOfUpdates++; - } - - for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeOriginalDelete]) { - [super deleteItemsAtIndexPaths:change.indexPaths]; - numberOfUpdates++; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeOriginalDelete]) { - [super deleteSections:change.indexSet]; - numberOfUpdates++; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeOriginalInsert]) { - [super insertSections:change.indexSet]; - numberOfUpdates++; - } - - for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeOriginalInsert]) { - [super insertItemsAtIndexPaths:change.indexPaths]; - numberOfUpdates++; - } - } completion:completion]; - } - - as_log_debug(ASCollectionLog(), "Completed batch update %{public}@", self.collectionNode); - - // Flush any range changes that happened as part of submitting the update. - as_activity_scope(changeSet.rootActivity); - [_rangeController updateIfNeeded]; - } - }); -} - -#pragma mark - ASCellNodeDelegate - -- (void)nodeSelectedStateDidChange:(ASCellNode *)node -{ - NSIndexPath *indexPath = [self indexPathForNode:node]; - if (indexPath) { - if (node.isSelected) { - [super selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone]; - } else { - [super deselectItemAtIndexPath:indexPath animated:NO]; - } - } -} - -- (void)nodeHighlightedStateDidChange:(ASCellNode *)node -{ - NSIndexPath *indexPath = [self indexPathForNode:node]; - if (indexPath) { - [self cellForItemAtIndexPath:indexPath].highlighted = node.isHighlighted; - } -} - -- (void)nodeDidInvalidateSize:(ASCellNode *)node -{ - [_cellsForLayoutUpdates addObject:node]; - [self setNeedsLayout]; -} - -- (void)nodesDidRelayout:(NSArray *)nodes -{ - ASDisplayNodeAssertMainThread(); - - if (nodes.count == 0) { - return; - } - - const auto uikitIndexPaths = ASArrayByFlatMapping(nodes, ASCellNode *node, [self indexPathForNode:node]); - - [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:uikitIndexPaths batched:NO]; - - ASCollectionViewInvalidationStyle invalidationStyle = _nextLayoutInvalidationStyle; - for (ASCellNode *node in nodes) { - if (invalidationStyle == ASCollectionViewInvalidationStyleNone) { - // We nodesDidRelayout also while we are in layoutSubviews. This should be no problem as CA will ignore this - // call while be in a layout pass - [self setNeedsLayout]; - invalidationStyle = ASCollectionViewInvalidationStyleWithAnimation; - } - - // If we think we're going to animate, check if this node will prevent it. - if (invalidationStyle == ASCollectionViewInvalidationStyleWithAnimation) { - // TODO: Incorporate `shouldAnimateSizeChanges` into ASEnvironmentState for performance benefit. - static dispatch_once_t onceToken; - static BOOL (^shouldNotAnimateBlock)(ASDisplayNode *); - dispatch_once(&onceToken, ^{ - shouldNotAnimateBlock = ^BOOL(ASDisplayNode * _Nonnull node) { - return (node.shouldAnimateSizeChanges == NO); - }; - }); - if (ASDisplayNodeFindFirstNode(node, shouldNotAnimateBlock) != nil) { - // One single non-animated node causes the whole layout update to be non-animated - invalidationStyle = ASCollectionViewInvalidationStyleWithoutAnimation; - break; - } - } - } - _nextLayoutInvalidationStyle = invalidationStyle; -} - -#pragma mark - _ASDisplayView behavior substitutions -// Need these to drive interfaceState so we know when we are visible, if not nested in another range-managing element. -// Because our superclass is a true UIKit class, we cannot also subclass _ASDisplayView. -- (void)willMoveToWindow:(UIWindow *)newWindow -{ - BOOL visible = (newWindow != nil); - ASDisplayNode *node = self.collectionNode; - if (visible && !node.inHierarchy) { - [node __enterHierarchy]; - } -} - -- (void)didMoveToWindow -{ - BOOL visible = (self.window != nil); - ASDisplayNode *node = self.collectionNode; - BOOL rangeControllerNeedsUpdate = ![node supportsRangeManagedInterfaceState];; - - if (!visible && node.inHierarchy) { - if (rangeControllerNeedsUpdate) { - rangeControllerNeedsUpdate = NO; - // Exit CellNodes first before Collection to match UIKit behaviors (tear down bottom up). - // Although we have not yet cleared the interfaceState's Visible bit (this happens in __exitHierarchy), - // the ASRangeController will get the correct value from -interfaceStateForRangeController:. - [_rangeController updateRanges]; - } - [node __exitHierarchy]; - } - - // Updating the visible node index paths only for not range managed nodes. Range managed nodes will get their - // their update in the layout pass - if (rangeControllerNeedsUpdate) { - [_rangeController updateRanges]; - } - - // When we aren't visible, we will only fetch up to the visible area. Now that we are visible, - // we will fetch visible area + leading screens, so we need to check. - if (visible) { - [self _checkForBatchFetching]; - } -} - -- (void)willMoveToSuperview:(UIView *)newSuperview -{ - if (self.superview == nil && newSuperview != nil) { - _keepalive_node = self.collectionNode; - } -} - -- (void)didMoveToSuperview -{ - if (self.superview == nil) { - _keepalive_node = nil; - } -} - -#pragma mark ASCALayerExtendedDelegate - -/** - * TODO: This code was added when we used @c calculatedSize as the size for - * items (e.g. collectionView:layout:sizeForItemAtIndexPath:) and so it - * was critical that we remeasured all nodes at this time. - * - * The assumption was that cv-bounds-size-change -> constrained-size-change, so - * this was the time when we get new constrained sizes for all items and remeasure - * them. However, the constrained sizes for items can be invalidated for many other - * reasons, hence why we never reuse the old constrained size anymore. - * - * UICollectionView inadvertently triggers a -prepareLayout call to its layout object - * between [super setFrame:] and [self layoutSubviews] during size changes. So we need - * to get in there and re-measure our nodes before that -prepareLayout call. - * We can't wait until -layoutSubviews or the end of -setFrame:. - * - * @see @p testThatNodeCalculatedSizesAreUpdatedBeforeFirstPrepareLayoutAfterRotation - */ -- (void)layer:(CALayer *)layer didChangeBoundsWithOldValue:(CGRect)oldBounds newValue:(CGRect)newBounds -{ - CGSize newSize = newBounds.size; - CGSize lastUsedSize = _lastBoundsSizeUsedForMeasuringNodes; - if (CGSizeEqualToSize(lastUsedSize, newSize)) { - return; - } - if (_hasDataControllerLayoutDelegate || self.collectionViewLayout == nil) { - // Let the layout delegate handle bounds changes if it's available. If no layout, it will init in the new state. - return; - } - - _lastBoundsSizeUsedForMeasuringNodes = newSize; - - // Laying out all nodes is expensive. - // We only need to do this if the bounds changed in the non-scrollable direction. - // If, for example, a vertical flow layout has its height changed due to a status bar - // appearance update, we do not need to relayout all nodes. - // For a more permanent fix to the unsafety mentioned above, see https://github.com/facebook/AsyncDisplayKit/pull/2182 - ASScrollDirection scrollDirection = self.scrollableDirections; - BOOL fixedVertically = (ASScrollDirectionContainsVerticalDirection (scrollDirection) == NO); - BOOL fixedHorizontally = (ASScrollDirectionContainsHorizontalDirection(scrollDirection) == NO); - - BOOL changedInNonScrollingDirection = (fixedHorizontally && newSize.width != lastUsedSize.width) || - (fixedVertically && newSize.height != lastUsedSize.height); - - if (changedInNonScrollingDirection) { - [self relayoutItems]; - } -} - -#pragma mark - UICollectionView dead-end intercepts - -- (void)setPrefetchDataSource:(id)prefetchDataSource -{ - return; -} - -- (void)setPrefetchingEnabled:(BOOL)prefetchingEnabled -{ - return; -} - -#pragma mark - Accessibility overrides - -- (NSArray *)accessibilityElements -{ - [self waitUntilAllUpdatesAreCommitted]; - return [super accessibilityElements]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionViewLayoutFacilitatorProtocol.h b/submodules/AsyncDisplayKit/Source/ASCollectionViewLayoutFacilitatorProtocol.h deleted file mode 100644 index b60dd0e02f..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionViewLayoutFacilitatorProtocol.h +++ /dev/null @@ -1,48 +0,0 @@ -// -// ASCollectionViewLayoutFacilitatorProtocol.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#pragma once -#import - -/** - * This facilitator protocol is intended to help Layout to better - * gel with the CollectionView - */ -@protocol ASCollectionViewLayoutFacilitatorProtocol - -/** - * Inform that the collectionView is editing the cells at a list of indexPaths - * - * @param indexPaths an array of NSIndexPath objects of cells being/will be edited. - * @param isBatched indicates whether the editing operation will be batched by the collectionView - * - * NOTE: when isBatched, used in combination with -collectionViewWillPerformBatchUpdates - */ -- (void)collectionViewWillEditCellsAtIndexPaths:(NSArray *)indexPaths batched:(BOOL)isBatched; - -/** - * Inform that the collectionView is editing the sections at a set of indexes - * - * @param indexes an NSIndexSet of section indexes being/will be edited. - * @param batched indicates whether the editing operation will be batched by the collectionView - * - * NOTE: when batched, used in combination with -collectionViewWillPerformBatchUpdates - */ -- (void)collectionViewWillEditSectionsAtIndexSet:(NSIndexSet *)indexes batched:(BOOL)batched; - -/** - * Informs the delegate that the collectionView is about to call performBatchUpdates - */ -- (void)collectionViewWillPerformBatchUpdates; - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionViewProtocols.h b/submodules/AsyncDisplayKit/Source/ASCollectionViewProtocols.h deleted file mode 100644 index 459e12efcd..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionViewProtocols.h +++ /dev/null @@ -1,103 +0,0 @@ -// -// ASCollectionViewProtocols.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK -#import -#import - -typedef NS_OPTIONS(NSUInteger, ASCellLayoutMode) { - /** - * No options set. If cell layout mode is set to ASCellLayoutModeNone, the default values for - * each flag listed below is used. - */ - ASCellLayoutModeNone = 0, - /** - * If ASCellLayoutModeAlwaysSync is enabled it will cause the ASDataController to wait on the - * background queue, and this ensures that any new / changed cells are in the hierarchy by the - * very next CATransaction / frame draw. - * - * Note: Sync & Async flags force the behavior to be always one or the other, regardless of the - * items. Default: If neither ASCellLayoutModeAlwaysSync or ASCellLayoutModeAlwaysAsync is set, - * default behavior is synchronous when there are 0 or 1 ASCellNodes in the data source, and - * asynchronous when there are 2 or more. - */ - ASCellLayoutModeAlwaysSync = 1 << 1, // Default OFF - ASCellLayoutModeAlwaysAsync = 1 << 2, // Default OFF - ASCellLayoutModeForceIfNeeded = 1 << 3, // Deprecated, default OFF. - ASCellLayoutModeAlwaysPassthroughDelegate = 1 << 4, // Deprecated, default ON. - /** Instead of using performBatchUpdates: prefer using reloadData for changes for collection view */ - ASCellLayoutModeAlwaysReloadData = 1 << 5, // Default OFF - /** If flag is enabled nodes are *not* gonna be range managed. */ - ASCellLayoutModeDisableRangeController = 1 << 6, // Default OFF - ASCellLayoutModeAlwaysLazy = 1 << 7, // Deprecated, default OFF. - /** - * Defines if the node creation should happen serialized and not in parallel within the - * data controller - */ - ASCellLayoutModeSerializeNodeCreation = 1 << 8, // Default OFF - /** - * When set, the performBatchUpdates: API (including animation) is used when handling Section - * Reload operations. This is useful only when ASCellLayoutModeAlwaysReloadData is enabled and - * cell height animations are desired. - */ - ASCellLayoutModeAlwaysBatchUpdateSectionReload = 1 << 9, // Default OFF -}; - -NS_ASSUME_NONNULL_BEGIN - -/** - * This is a subset of UICollectionViewDataSource. - * - * @see ASCollectionDataSource - */ -@protocol ASCommonCollectionDataSource - -@optional - -- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section ASDISPLAYNODE_DEPRECATED_MSG("Implement -collectionNode:numberOfItemsInSection: instead."); - -- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView ASDISPLAYNODE_DEPRECATED_MSG("Implement -numberOfSectionsInCollectionNode: instead."); - -- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement - collectionNode:nodeForSupplementaryElementOfKind:atIndexPath: instead."); - -@end - - -/** - * This is a subset of UICollectionViewDelegate. - * - * @see ASCollectionDelegate - */ -@protocol ASCommonCollectionDelegate - -@optional - -- (UICollectionViewTransitionLayout *)collectionView:(UICollectionView *)collectionView transitionLayoutForOldLayout:(UICollectionViewLayout *)fromLayout newLayout:(UICollectionViewLayout *)toLayout; - -- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -collectionNode:willDisplaySupplementaryView:forElementKind:atIndexPath: instead."); -- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -collectionNode:didEndDisplayingSupplementaryView:forElementKind:atIndexPath: instead."); - -- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:shouldHighlightItemAtIndexPath: instead."); -- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:didHighlightItemAtIndexPath: instead."); -- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:didUnhighlightItemAtIndexPath: instead."); - -- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:shouldSelectItemAtIndexPath: instead."); -- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:didSelectItemAtIndexPath: instead."); -- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:shouldDeselectItemAtIndexPath: instead."); -- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:didDeselectItemAtIndexPath: instead."); - -- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:shouldShowMenuForItemAtIndexPath: instead."); -- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:canPerformAction:forItemAtIndexPath:withSender: instead."); -- (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:performAction:forItemAtIndexPath:withSender: instead."); - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASConfigurationInternal.mm b/submodules/AsyncDisplayKit/Source/ASConfigurationInternal.mm index 485fef0c58..2fb190103a 100644 --- a/submodules/AsyncDisplayKit/Source/ASConfigurationInternal.mm +++ b/submodules/AsyncDisplayKit/Source/ASConfigurationInternal.mm @@ -6,7 +6,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASConfigurationInternal.h" +#import #import #import #import diff --git a/submodules/AsyncDisplayKit/Source/Private/ASControlNode+Private.h b/submodules/AsyncDisplayKit/Source/ASControlNode+Private.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/Private/ASControlNode+Private.h rename to submodules/AsyncDisplayKit/Source/ASControlNode+Private.h diff --git a/submodules/AsyncDisplayKit/Source/ASControlNode.mm b/submodules/AsyncDisplayKit/Source/ASControlNode.mm index 1256d2d629..dcdb9ec829 100644 --- a/submodules/AsyncDisplayKit/Source/ASControlNode.mm +++ b/submodules/AsyncDisplayKit/Source/ASControlNode.mm @@ -8,11 +8,9 @@ // #import -#import +#import "ASControlNode+Private.h" #import #import -#import -#import #import #import #import @@ -73,7 +71,6 @@ CGRect _ASControlNodeGetExpandedBounds(ASControlNode *controlNode); @implementation ASControlNode { - ASImageNode *_debugHighlightOverlay; } #pragma mark - Lifecycle @@ -292,20 +289,6 @@ CGRect _ASControlNodeGetExpandedBounds(ASControlNode *controlNode); if (!_controlEventDispatchTable) { _controlEventDispatchTable = [[NSMutableDictionary alloc] initWithCapacity:kASControlNodeEventDispatchTableInitialCapacity]; // enough to handle common types without re-hashing the dictionary when adding entries. - - // only show tap-able areas for views with 1 or more addTarget:action: pairs - if ([ASControlNode enableHitTestDebug] && _debugHighlightOverlay == nil) { - // do not use ASPerformBlockOnMainThread here, if it performs the block synchronously it will continue - // holding the lock while calling addSubnode. - dispatch_async(dispatch_get_main_queue(), ^{ - // add a highlight overlay node with area of ASControlNode + UIEdgeInsets - self.clipsToBounds = NO; - _debugHighlightOverlay = [[ASImageNode alloc] init]; - _debugHighlightOverlay.zPosition = 1000; // ensure we're over the top of any siblings - _debugHighlightOverlay.layerBacked = YES; - [self addSubnode:_debugHighlightOverlay]; - }); - } } // Create new target action pair @@ -509,8 +492,8 @@ CGRect _ASControlNodeGetExpandedBounds(ASControlNode *controlNode) { } #pragma mark - Debug -- (ASImageNode *)debugHighlightOverlay +- (ASDisplayNode *)debugHighlightOverlay { - return _debugHighlightOverlay; + return nil; } @end diff --git a/submodules/AsyncDisplayKit/Source/Private/ASControlTargetAction.mm b/submodules/AsyncDisplayKit/Source/ASControlTargetAction.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/Private/ASControlTargetAction.mm rename to submodules/AsyncDisplayKit/Source/ASControlTargetAction.mm diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASDimension.mm b/submodules/AsyncDisplayKit/Source/ASDimension.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/Layout/ASDimension.mm rename to submodules/AsyncDisplayKit/Source/ASDimension.mm diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASDimensionInternal.mm b/submodules/AsyncDisplayKit/Source/ASDimensionInternal.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/Layout/ASDimensionInternal.mm rename to submodules/AsyncDisplayKit/Source/ASDimensionInternal.mm diff --git a/submodules/AsyncDisplayKit/Source/Private/ASDispatch.h b/submodules/AsyncDisplayKit/Source/ASDispatch.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/Private/ASDispatch.h rename to submodules/AsyncDisplayKit/Source/ASDispatch.h diff --git a/submodules/AsyncDisplayKit/Source/Private/ASDispatch.mm b/submodules/AsyncDisplayKit/Source/ASDispatch.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/Private/ASDispatch.mm rename to submodules/AsyncDisplayKit/Source/ASDispatch.mm index 769a9185d4..edc2feba46 100644 --- a/submodules/AsyncDisplayKit/Source/Private/ASDispatch.mm +++ b/submodules/AsyncDisplayKit/Source/ASDispatch.mm @@ -6,7 +6,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import +#import "ASDispatch.h" #import diff --git a/submodules/AsyncDisplayKit/Source/Base/ASDisplayNode+Ancestry.mm b/submodules/AsyncDisplayKit/Source/ASDisplayNode+Ancestry.mm similarity index 97% rename from submodules/AsyncDisplayKit/Source/Base/ASDisplayNode+Ancestry.mm rename to submodules/AsyncDisplayKit/Source/ASDisplayNode+Ancestry.mm index 7003064c70..ea1376ed54 100644 --- a/submodules/AsyncDisplayKit/Source/Base/ASDisplayNode+Ancestry.mm +++ b/submodules/AsyncDisplayKit/Source/ASDisplayNode+Ancestry.mm @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASDisplayNode+Ancestry.h" +#import #import #import diff --git a/submodules/AsyncDisplayKit/Source/Private/ASDisplayNode+AsyncDisplay.mm b/submodules/AsyncDisplayKit/Source/ASDisplayNode+AsyncDisplay.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/Private/ASDisplayNode+AsyncDisplay.mm rename to submodules/AsyncDisplayKit/Source/ASDisplayNode+AsyncDisplay.mm index 4efd2b0a79..068f5509a7 100644 --- a/submodules/AsyncDisplayKit/Source/Private/ASDisplayNode+AsyncDisplay.mm +++ b/submodules/AsyncDisplayKit/Source/ASDisplayNode+AsyncDisplay.mm @@ -11,11 +11,11 @@ #import #import #import -#import +#import "ASDisplayNodeInternal.h" #import #import #import -#import +#import "ASSignpost.h" #import using AS::MutexLocker; diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Convenience.mm b/submodules/AsyncDisplayKit/Source/ASDisplayNode+Convenience.mm index 42ae4e4a72..29d761be5a 100644 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Convenience.mm +++ b/submodules/AsyncDisplayKit/Source/ASDisplayNode+Convenience.mm @@ -7,12 +7,12 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASDisplayNode+Convenience.h" +#import #import #import -#import +#import "ASResponderChainEnumerator.h" @implementation ASDisplayNode (Convenience) diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Layout.mm b/submodules/AsyncDisplayKit/Source/ASDisplayNode+Layout.mm index b7956541b4..32934a599c 100644 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Layout.mm +++ b/submodules/AsyncDisplayKit/Source/ASDisplayNode+Layout.mm @@ -10,15 +10,12 @@ #import #import #import -#import +#import "ASDisplayNodeInternal.h" #import #import #import #import -#import -#import -#import -#import +#import "ASLayoutElementStylePrivate.h" #import using AS::MutexLocker; @@ -97,7 +94,6 @@ using AS::MutexLocker; layout = [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize]; - as_log_verbose(ASLayoutLog(), "Established pending layout for %@ in %s", self, sel_getName(_cmd)); _pendingDisplayNodeLayout = ASDisplayNodeLayout(layout, constrainedSize, parentSize,version); ASDisplayNodeAssertNotNil(layout, @"-[ASDisplayNode layoutThatFits:parentSize:] newly calculated layout should not be nil! %@", self); } @@ -230,8 +226,6 @@ ASLayoutElementStyleExtensibilityForwarding ASDisplayNodeAssertThreadAffinity(self); ASAssertUnlocked(__instanceLock__); - as_activity_create_for_scope("Set needs layout from above"); - // Mark the node for layout in the next layout pass [self setNeedsLayout]; @@ -346,13 +340,6 @@ ASLayoutElementStyleExtensibilityForwarding if (!pendingLayoutIsPreferred && calculatedLayoutIsReusable) { return; } - - as_activity_create_for_scope("Update node layout for current bounds"); - as_log_verbose(ASLayoutLog(), "Node %@, bounds size %@, calculatedSize %@, calculatedIsDirty %d", - self, - NSStringFromCGSize(boundsSizeForLayout), - NSStringFromCGSize(_calculatedDisplayNodeLayout.layout.size), - _calculatedDisplayNodeLayout.version < _layoutVersion); // _calculatedDisplayNodeLayout is not reusable we need to transition to a new one [self cancelLayoutTransition]; @@ -372,18 +359,13 @@ ASLayoutElementStyleExtensibilityForwarding // If our bounds size is different than it, or invalid, recalculate. Use #define to avoid nullptr-> BOOL pendingLayoutApplicable = NO; if (nextLayout.layout == nil) { - as_log_verbose(ASLayoutLog(), "No pending layout."); } else if (!nextLayout.isValid(_layoutVersion)) { - as_log_verbose(ASLayoutLog(), "Pending layout is stale."); } else if (layoutSizeDifferentFromBounds) { - as_log_verbose(ASLayoutLog(), "Pending layout size %@ doesn't match bounds size.", NSStringFromCGSize(nextLayout.layout.size)); } else { - as_log_verbose(ASLayoutLog(), "Using pending layout %@.", nextLayout.layout); pendingLayoutApplicable = YES; } if (!pendingLayoutApplicable) { - as_log_verbose(ASLayoutLog(), "Measuring with previous constrained size."); // Use the last known constrainedSize passed from a parent during layout (if never, use bounds). NSUInteger version = _layoutVersion; ASSizeRange constrainedSize = [self _locked_constrainedSizeForLayoutPass]; @@ -404,7 +386,6 @@ ASLayoutElementStyleExtensibilityForwarding // This can occur for either pre-calculated or newly-calculated layouts. if (nextLayout.requestedLayoutFromAbove == NO && CGSizeEqualToSize(boundsSizeForLayout, nextLayout.layout.size) == NO) { - as_log_verbose(ASLayoutLog(), "Layout size doesn't match bounds size. Requesting layout from above."); // The layout that we have specifies that this node (self) would like to be a different size // than it currently is. Because that size has been computed within the constrainedSize, we // expect that calling setNeedsLayoutFromAbove will result in our parent resizing us to this. @@ -579,13 +560,10 @@ ASLayoutElementStyleExtensibilityForwarding measurementCompletion:(void(^)())completion { ASDisplayNodeAssertMainThread(); - as_activity_create_for_scope("Transition node layout"); - as_log_debug(ASLayoutLog(), "Transition layout for %@ sizeRange %@ anim %d asyncMeasure %d", self, NSStringFromASSizeRange(constrainedSize), animated, shouldMeasureAsync); if (constrainedSize.max.width <= 0.0 || constrainedSize.max.height <= 0.0) { // Using CGSizeZero for the sizeRange can cause negative values in client layout code. // Most likely called transitionLayout: without providing a size, before first layout pass. - as_log_verbose(ASLayoutLog(), "Ignoring transition due to bad size range."); return; } @@ -613,12 +591,10 @@ ASLayoutElementStyleExtensibilityForwarding // Every new layout transition has a transition id associated to check in subsequent transitions for cancelling int32_t transitionID = [self _startNewTransition]; - as_log_verbose(ASLayoutLog(), "Transition ID is %d", transitionID); // NOTE: This block captures self. It's cheaper than hitting the weak table. asdisplaynode_iscancelled_block_t isCancelled = ^{ BOOL result = (_transitionID != transitionID); if (result) { - as_log_verbose(ASLayoutLog(), "Transition %d canceled, superseded by %d", transitionID, _transitionID.load()); } return result; }; @@ -666,7 +642,6 @@ ASLayoutElementStyleExtensibilityForwarding if (isCancelled()) { return; } - as_activity_create_for_scope("Commit layout transition"); ASLayoutTransition *pendingLayoutTransition; _ASTransitionContext *pendingLayoutTransitionContext; { @@ -694,7 +669,6 @@ ASLayoutElementStyleExtensibilityForwarding // Apply complete layout transitions for all subnodes { - as_activity_create_for_scope("Complete pending layout transitions for subtree"); ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) { [node _completePendingLayoutTransition]; node.hierarchyState &= (~ASHierarchyStateLayoutPending); @@ -713,7 +687,6 @@ ASLayoutElementStyleExtensibilityForwarding // Kick off animating the layout transition { - as_activity_create_for_scope("Animate layout transition"); [self animateLayoutTransition:pendingLayoutTransitionContext]; } diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+LayoutSpec.mm b/submodules/AsyncDisplayKit/Source/ASDisplayNode+LayoutSpec.mm index d79073ffdd..132bc666f8 100644 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode+LayoutSpec.mm +++ b/submodules/AsyncDisplayKit/Source/ASDisplayNode+LayoutSpec.mm @@ -9,13 +9,12 @@ #import #import -#import -#import +#import "_ASScopeTimer.h" +#import "ASDisplayNodeInternal.h" #import #import -#import -#import -#import +#import "ASLayoutSpec+Subclasses.h" +#import "ASLayoutSpecPrivate.h" #import diff --git a/submodules/AsyncDisplayKit/Source/Private/ASDisplayNode+UIViewBridge.mm b/submodules/AsyncDisplayKit/Source/ASDisplayNode+UIViewBridge.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/Private/ASDisplayNode+UIViewBridge.mm rename to submodules/AsyncDisplayKit/Source/ASDisplayNode+UIViewBridge.mm index a6d60a7748..084800a1ed 100644 --- a/submodules/AsyncDisplayKit/Source/Private/ASDisplayNode+UIViewBridge.mm +++ b/submodules/AsyncDisplayKit/Source/ASDisplayNode+UIViewBridge.mm @@ -8,12 +8,12 @@ // #import -#import +#import "_ASPendingState.h" #import -#import +#import "ASDisplayNodeInternal.h" #import #import -#import +#import "ASPendingStateController.h" /** * The following macros are conveniences to help in the common tasks related to the bridging that ASDisplayNode does to UIView and CALayer. diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Yoga.h b/submodules/AsyncDisplayKit/Source/ASDisplayNode+Yoga.h deleted file mode 100644 index 93cbf7a7c1..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Yoga.h +++ /dev/null @@ -1,102 +0,0 @@ -// -// ASDisplayNode+Yoga.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if YOGA - -NS_ASSUME_NONNULL_BEGIN - -@class ASLayout; - -AS_EXTERN void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode * _Nullable node, void(^block)(ASDisplayNode *node)); - -@interface ASDisplayNode (Yoga) - -@property (copy) NSArray *yogaChildren; - -- (void)addYogaChild:(ASDisplayNode *)child; -- (void)removeYogaChild:(ASDisplayNode *)child; -- (void)insertYogaChild:(ASDisplayNode *)child atIndex:(NSUInteger)index; - -- (void)semanticContentAttributeDidChange:(UISemanticContentAttribute)attribute; - -@property BOOL yogaLayoutInProgress; -// TODO: Make this atomic (lock). -@property (nullable, nonatomic) ASLayout *yogaCalculatedLayout; - -// Will walk up the Yoga tree and returns the root node -- (ASDisplayNode *)yogaRoot; - - -@end - -@interface ASDisplayNode (YogaLocking) -/** - * @discussion Attempts(spinning) to lock all node up to root node when yoga is enabled. - * This will lock self when yoga is not enabled; - */ -- (ASLockSet)lockToRootIfNeededForLayout; - -@end - - -// These methods are intended to be used internally to Texture, and should not be called directly. -@interface ASDisplayNode (YogaInternal) - -/// For internal usage only -- (BOOL)shouldHaveYogaMeasureFunc; -/// For internal usage only -- (ASLayout *)calculateLayoutYoga:(ASSizeRange)constrainedSize; -/// For internal usage only -- (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize; -/// For internal usage only -- (void)invalidateCalculatedYogaLayout; -/** - * @discussion return true only when yoga enabled and the node is in yoga tree and the node is - * not leaf that implemented measure function. - */ -- (BOOL)locked_shouldLayoutFromYogaRoot; - -@end - -@interface ASDisplayNode (YogaDebugging) - -- (NSString *)yogaTreeDescription; - -@end - -@interface ASLayoutElementStyle (Yoga) - -- (YGNodeRef)yogaNodeCreateIfNeeded; -- (void)destroyYogaNode; - -@property (readonly) YGNodeRef yogaNode; - -@property ASStackLayoutDirection flexDirection; -@property YGDirection direction; -@property ASStackLayoutJustifyContent justifyContent; -@property ASStackLayoutAlignItems alignItems; -@property YGPositionType positionType; -@property ASEdgeInsets position; -@property ASEdgeInsets margin; -@property ASEdgeInsets padding; -@property ASEdgeInsets border; -@property CGFloat aspectRatio; -@property YGWrap flexWrap; - -@end - -NS_ASSUME_NONNULL_END - -// When Yoga is enabled, there are several points where we want to lock the tree to the root but otherwise (without Yoga) -// will want to simply lock self. -#define ASScopedLockSelfOrToRoot() ASScopedLockSet lockSet = [self lockToRootIfNeededForLayout] -#else -#define ASScopedLockSelfOrToRoot() ASLockScopeSelf() -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Yoga.mm b/submodules/AsyncDisplayKit/Source/ASDisplayNode+Yoga.mm deleted file mode 100644 index dcecda7279..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Yoga.mm +++ /dev/null @@ -1,472 +0,0 @@ -// -// ASDisplayNode+Yoga.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if YOGA /* YOGA */ - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#import - -#define YOGA_LAYOUT_LOGGING 0 - -#pragma mark - ASDisplayNode+Yoga - -@interface ASDisplayNode (YogaPrivate) -@property (nonatomic, weak) ASDisplayNode *yogaParent; -- (ASSizeRange)_locked_constrainedSizeForLayoutPass; -@end - -@implementation ASDisplayNode (Yoga) - -- (ASDisplayNode *)yogaRoot -{ - ASDisplayNode *yogaRoot = self; - ASDisplayNode *yogaParent = nil; - while ((yogaParent = yogaRoot.yogaParent)) { - yogaRoot = yogaParent; - } - return yogaRoot; -} - -- (void)setYogaChildren:(NSArray *)yogaChildren -{ - ASScopedLockSelfOrToRoot(); - for (ASDisplayNode *child in [_yogaChildren copy]) { - // Make sure to un-associate the YGNodeRef tree before replacing _yogaChildren - // If this becomes a performance bottleneck, it can be optimized by not doing the NSArray removals here. - [self _locked_removeYogaChild:child]; - } - _yogaChildren = nil; - for (ASDisplayNode *child in yogaChildren) { - [self _locked_addYogaChild:child]; - } -} - -- (NSArray *)yogaChildren -{ - ASLockScope(self.yogaRoot); - return [_yogaChildren copy] ?: @[]; -} - -- (void)addYogaChild:(ASDisplayNode *)child -{ - ASScopedLockSelfOrToRoot(); - [self _locked_addYogaChild:child]; -} - -- (void)_locked_addYogaChild:(ASDisplayNode *)child -{ - [self insertYogaChild:child atIndex:_yogaChildren.count]; -} - -- (void)removeYogaChild:(ASDisplayNode *)child -{ - ASScopedLockSelfOrToRoot(); - [self _locked_removeYogaChild:child]; -} - -- (void)_locked_removeYogaChild:(ASDisplayNode *)child -{ - if (child == nil) { - return; - } - - [_yogaChildren removeObjectIdenticalTo:child]; - - // YGNodeRef removal is done in setParent: - child.yogaParent = nil; -} - -- (void)insertYogaChild:(ASDisplayNode *)child atIndex:(NSUInteger)index -{ - ASScopedLockSelfOrToRoot(); - [self _locked_insertYogaChild:child atIndex:index]; -} - -- (void)_locked_insertYogaChild:(ASDisplayNode *)child atIndex:(NSUInteger)index -{ - if (child == nil) { - return; - } - if (_yogaChildren == nil) { - _yogaChildren = [[NSMutableArray alloc] init]; - } - - // Clean up state in case this child had another parent. - [self _locked_removeYogaChild:child]; - - [_yogaChildren insertObject:child atIndex:index]; - - // YGNodeRef insertion is done in setParent: - child.yogaParent = self; -} - -#pragma mark - Subclass Hooks - -- (void)semanticContentAttributeDidChange:(UISemanticContentAttribute)attribute -{ - UIUserInterfaceLayoutDirection layoutDirection = - [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:attribute]; - self.style.direction = (layoutDirection == UIUserInterfaceLayoutDirectionLeftToRight - ? YGDirectionLTR : YGDirectionRTL); -} - -- (void)setYogaParent:(ASDisplayNode *)yogaParent -{ - ASLockScopeSelf(); - if (_yogaParent == yogaParent) { - return; - } - - YGNodeRef yogaNode = [self.style yogaNodeCreateIfNeeded]; - YGNodeRef oldParentRef = YGNodeGetParent(yogaNode); - if (oldParentRef != NULL) { - YGNodeRemoveChild(oldParentRef, yogaNode); - } - - _yogaParent = yogaParent; - if (yogaParent) { - YGNodeRef newParentRef = [yogaParent.style yogaNodeCreateIfNeeded]; - YGNodeInsertChild(newParentRef, yogaNode, YGNodeGetChildCount(newParentRef)); - } -} - -- (ASDisplayNode *)yogaParent -{ - return _yogaParent; -} - -- (void)setYogaCalculatedLayout:(ASLayout *)yogaCalculatedLayout -{ - _yogaCalculatedLayout = yogaCalculatedLayout; -} - -- (ASLayout *)yogaCalculatedLayout -{ - return _yogaCalculatedLayout; -} - -- (void)setYogaLayoutInProgress:(BOOL)yogaLayoutInProgress -{ - setFlag(YogaLayoutInProgress, yogaLayoutInProgress); - [self updateYogaMeasureFuncIfNeeded]; -} - -- (BOOL)yogaLayoutInProgress -{ - return checkFlag(YogaLayoutInProgress); -} - -- (ASLayout *)layoutForYogaNode -{ - YGNodeRef yogaNode = self.style.yogaNode; - - CGSize size = CGSizeMake(YGNodeLayoutGetWidth(yogaNode), YGNodeLayoutGetHeight(yogaNode)); - CGPoint position = CGPointMake(YGNodeLayoutGetLeft(yogaNode), YGNodeLayoutGetTop(yogaNode)); - - if (!ASIsCGSizeValidForSize(size)) { - size = CGSizeZero; - } - - if (!ASIsCGPositionValidForLayout(position)) { - position = CGPointZero; - } - return [ASLayout layoutWithLayoutElement:self size:size position:position sublayouts:nil]; -} - -- (void)setupYogaCalculatedLayout -{ - ASScopedLockSelfOrToRoot(); - - YGNodeRef yogaNode = self.style.yogaNode; - uint32_t childCount = YGNodeGetChildCount(yogaNode); - ASDisplayNodeAssert(childCount == _yogaChildren.count, - @"Yoga tree should always be in sync with .yogaNodes array! %@", - _yogaChildren); - - ASLayout *rawSublayouts[childCount]; - int i = 0; - for (ASDisplayNode *subnode in _yogaChildren) { - rawSublayouts[i++] = [subnode layoutForYogaNode]; - } - const auto sublayouts = [NSArray arrayByTransferring:rawSublayouts count:childCount]; - - // The layout for self should have position CGPointNull, but include the calculated size. - CGSize size = CGSizeMake(YGNodeLayoutGetWidth(yogaNode), YGNodeLayoutGetHeight(yogaNode)); - if (!ASIsCGSizeValidForSize(size)) { - size = CGSizeZero; - } - ASLayout *layout = [ASLayout layoutWithLayoutElement:self size:size sublayouts:sublayouts]; - -#if ASDISPLAYNODE_ASSERTIONS_ENABLED - // Assert that the sublayout is already flattened. - for (ASLayout *sublayout in layout.sublayouts) { - if (sublayout.sublayouts.count > 0 || ASDynamicCast(sublayout.layoutElement, ASDisplayNode) == nil) { - ASDisplayNodeAssert(NO, @"Yoga sublayout is not flattened! %@, %@", self, sublayout); - } - } -#endif - - // Because this layout won't go through the rest of the logic in calculateLayoutThatFits:, flatten it now. - layout = [layout filteredNodeLayoutTree]; - - if ([self.yogaCalculatedLayout isEqual:layout] == NO) { - self.yogaCalculatedLayout = layout; - } else { - layout = self.yogaCalculatedLayout; - ASYogaLog("-setupYogaCalculatedLayout: applying identical ASLayout: %@", layout); - } - - // Setup _pendingDisplayNodeLayout to reference the Yoga-calculated ASLayout, *unless* we are a leaf node. - // Leaf yoga nodes may have their own .sublayouts, if they use a layout spec (such as ASButtonNode). - // Their _pending variable is set after passing the Yoga checks at the start of -calculateLayoutThatFits: - - // For other Yoga nodes, there is no code that will set _pending unless we do it here. Why does it need to be set? - // When CALayer triggers the -[ASDisplayNode __layout] call, we will check if our current _pending layout - // has a size which matches our current bounds size. If it does, that layout will be used without recomputing it. - - // NOTE: Yoga does not make the constrainedSize available to intermediate nodes in the tree (e.g. not root or leaves). - // Although the size range provided here is not accurate, this will only affect caching of calls to layoutThatFits: - // These calls will behave as if they are not cached, starting a new Yoga layout pass, but this will tap into Yoga's - // own internal cache. - - if ([self shouldHaveYogaMeasureFunc] == NO) { - YGNodeRef parentNode = YGNodeGetParent(yogaNode); - CGSize parentSize = CGSizeZero; - if (parentNode) { - parentSize.width = YGNodeLayoutGetWidth(parentNode); - parentSize.height = YGNodeLayoutGetHeight(parentNode); - } - // For the root node in a Yoga tree, make sure to preserve the constrainedSize originally provided. - // This will be used for all relayouts triggered by children, since they escalate to root. - ASSizeRange range = parentNode ? ASSizeRangeUnconstrained : self.constrainedSizeForCalculatedLayout; - _pendingDisplayNodeLayout = ASDisplayNodeLayout(layout, range, parentSize, _layoutVersion); - } -} - -- (BOOL)shouldHaveYogaMeasureFunc -{ - ASLockScopeSelf(); - // Size calculation via calculateSizeThatFits: or layoutSpecThatFits: - // For these nodes, we assume they may need custom Baseline calculation too. - // This will be used for ASTextNode, as well as any other node that has no Yoga children - BOOL isLeafNode = (_yogaChildren.count == 0); - BOOL definesCustomLayout = [self implementsLayoutMethod]; - return (isLeafNode && definesCustomLayout); -} - -- (void)updateYogaMeasureFuncIfNeeded -{ - // We set the measure func only during layout. Otherwise, a cycle is created: - // The YGNodeRef Context will retain the ASDisplayNode, which retains the style, which owns the YGNodeRef. - BOOL shouldHaveMeasureFunc = ([self shouldHaveYogaMeasureFunc] && checkFlag(YogaLayoutInProgress)); - - ASLayoutElementYogaUpdateMeasureFunc(self.style.yogaNode, shouldHaveMeasureFunc ? self : nil); -} - -- (void)invalidateCalculatedYogaLayout -{ - ASLockScopeSelf(); - YGNodeRef yogaNode = self.style.yogaNode; - if (yogaNode && [self shouldHaveYogaMeasureFunc]) { - // Yoga internally asserts that MarkDirty() may only be called on nodes with a measurement function. - BOOL needsTemporaryMeasureFunc = (YGNodeGetMeasureFunc(yogaNode) == NULL); - if (needsTemporaryMeasureFunc) { - ASDisplayNodeAssert(self.yogaLayoutInProgress == NO, - @"shouldHaveYogaMeasureFunc == YES, and inside a layout pass, but no measure func pointer! %@", self); - YGNodeSetMeasureFunc(yogaNode, &ASLayoutElementYogaMeasureFunc); - } - YGNodeMarkDirty(yogaNode); - if (needsTemporaryMeasureFunc) { - YGNodeSetMeasureFunc(yogaNode, NULL); - } - } - self.yogaCalculatedLayout = nil; -} - -- (ASLayout *)calculateLayoutYoga:(ASSizeRange)constrainedSize -{ - AS::UniqueLock l(__instanceLock__); - - // There are several cases where Yoga could arrive here: - // - This node is not in a Yoga tree: it has neither a yogaParent nor yogaChildren. - // - This node is a Yoga tree root: it has no yogaParent, but has yogaChildren. - // - This node is a Yoga tree node: it has both a yogaParent and yogaChildren. - // - This node is a Yoga tree leaf: it has a yogaParent, but no yogaChidlren. - if ([self locked_shouldLayoutFromYogaRoot]) { - // If we're a yoga root, tree node, or leaf with no measure func (e.g. spacer), then - // initiate a new Yoga calculation pass from root. - as_activity_create_for_scope("Yoga layout calculation"); - if (self.yogaLayoutInProgress == NO) { - ASYogaLog("Calculating yoga layout from root %@, %@", self, - NSStringFromASSizeRange(constrainedSize)); - [self calculateLayoutFromYogaRoot:constrainedSize]; - } else { - ASYogaLog("Reusing existing yoga layout %@", _yogaCalculatedLayout); - } - ASDisplayNodeAssert(_yogaCalculatedLayout, - @"Yoga node should have a non-nil layout at this stage: %@", self); - return _yogaCalculatedLayout; - } else { - // If we're a yoga leaf node with custom measurement function, proceed with normal layout so - // layoutSpecs can run (e.g. ASButtonNode). - ASYogaLog("PROCEEDING past Yoga check to calculate ASLayout for: %@", self); - } - - // Delegate to layout spec layout for nodes that do not support Yoga - return [self calculateLayoutLayoutSpec:constrainedSize]; -} - -- (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize -{ - ASScopedLockSet lockSet = [self lockToRootIfNeededForLayout]; - ASDisplayNode *yogaRoot = self.yogaRoot; - - if (self != yogaRoot) { - ASYogaLog("ESCALATING to Yoga root: %@", self); - // TODO(appleguy): Consider how to get the constrainedSize for the yogaRoot when escalating manually. - [yogaRoot calculateLayoutFromYogaRoot:ASSizeRangeUnconstrained]; - return; - } - - if (ASSizeRangeEqualToSizeRange(rootConstrainedSize, ASSizeRangeUnconstrained)) { - rootConstrainedSize = [self _locked_constrainedSizeForLayoutPass]; - } - - [self willCalculateLayout:rootConstrainedSize]; - [self enumerateInterfaceStateDelegates:^(id _Nonnull delegate) { - if ([delegate respondsToSelector:@selector(nodeWillCalculateLayout:)]) { - [delegate nodeWillCalculateLayout:rootConstrainedSize]; - } - }]; - - // Prepare all children for the layout pass with the current Yoga tree configuration. - ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode *_Nonnull node) { - node.yogaLayoutInProgress = YES; - ASDisplayNode *yogaParent = node.yogaParent; - if (yogaParent) { - node.style.parentAlignStyle = yogaParent.style.alignItems; - } else { - node.style.parentAlignStyle = ASStackLayoutAlignItemsNotSet; - }; - }); - - ASYogaLog("CALCULATING at Yoga root with constraint = {%@, %@}: %@", - NSStringFromCGSize(rootConstrainedSize.min), NSStringFromCGSize(rootConstrainedSize.max), self); - - YGNodeRef rootYogaNode = self.style.yogaNode; - - // Apply the constrainedSize as a base, known frame of reference. - // If the root node also has style.*Size set, these will be overridden below. - // YGNodeCalculateLayout currently doesn't offer the ability to pass a minimum size (max is passed there). - - // TODO(appleguy): Reconcile the self.style.*Size properties with rootConstrainedSize - YGNodeStyleSetMinWidth (rootYogaNode, yogaFloatForCGFloat(rootConstrainedSize.min.width)); - YGNodeStyleSetMinHeight(rootYogaNode, yogaFloatForCGFloat(rootConstrainedSize.min.height)); - - // It is crucial to use yogaFloat... to convert CGFLOAT_MAX into YGUndefined here. - YGNodeCalculateLayout(rootYogaNode, - yogaFloatForCGFloat(rootConstrainedSize.max.width), - yogaFloatForCGFloat(rootConstrainedSize.max.height), - YGDirectionInherit); - - // Reset accessible elements, since layout may have changed. - ASPerformBlockOnMainThread(^{ - if (self.nodeLoaded && !self.isSynchronous) { - [(_ASDisplayView *)self.view setAccessibilityElements:nil]; - } - }); - - ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) { - [node setupYogaCalculatedLayout]; - node.yogaLayoutInProgress = NO; - }); - -#if YOGA_LAYOUT_LOGGING /* YOGA_LAYOUT_LOGGING */ - // Concurrent layouts will interleave the NSLog messages unless we serialize. - // Use @synchornize rather than trampolining to the main thread so the tree state isn't changed. - @synchronized ([ASDisplayNode class]) { - NSLog(@"****************************************************************************"); - NSLog(@"******************** STARTING YOGA -> ASLAYOUT CREATION ********************"); - NSLog(@"****************************************************************************"); - ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) { - NSLog(@"node = %@", node); - YGNodePrint(node.style.yogaNode, (YGPrintOptions)(YGPrintOptionsStyle | YGPrintOptionsLayout)); - NSCAssert(ASIsCGSizeValidForSize(node.yogaCalculatedLayout.size), @"Yoga layout returned an invalid size"); - NSLog(@" "); // Newline - }); - } -#endif /* YOGA_LAYOUT_LOGGING */ -} - -@end - -#pragma mark - ASDisplayNode (YogaLocking) - -@implementation ASDisplayNode (YogaLocking) - -- (ASLockSet)lockToRootIfNeededForLayout { - ASLockSet lockSet = ASLockSequence(^BOOL(ASAddLockBlock addLock) { - if (!addLock(self)) { - return NO; - } -#if YOGA - if (![self locked_shouldLayoutFromYogaRoot]) { - return YES; - } - if (self.nodeController && !addLock(self.nodeController)) { - return NO; - } - ASDisplayNode *parent = _supernode; - while (parent) { - if (!addLock(parent)) { - return NO; - } - if (parent.nodeController && !addLock(parent.nodeController)) { - return NO; - } - parent = parent->_supernode; - } -#endif - return true; - }); - return lockSet; -} - -@end - -@implementation ASDisplayNode (YogaDebugging) - -- (NSString *)yogaTreeDescription { - return [self _yogaTreeDescription:@""]; -} - -- (NSString *)_yogaTreeDescription:(NSString *)indent { - auto subtree = [NSMutableString stringWithFormat:@"%@%@\n", indent, self.description]; - for (ASDisplayNode *n in self.yogaChildren) { - [subtree appendString:[n _yogaTreeDescription:[indent stringByAppendingString:@"| "]]]; - } - return subtree; -} - -@end - -#endif /* YOGA */ diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode.mm b/submodules/AsyncDisplayKit/Source/ASDisplayNode.mm index 8817bb8d8e..f17f390aff 100644 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode.mm +++ b/submodules/AsyncDisplayKit/Source/ASDisplayNode.mm @@ -7,47 +7,40 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import +#import "ASDisplayNodeInternal.h" #import #import #import -#import -#import -#import +#import "ASLayoutSpec+Subclasses.h" #import #include #import -#import +#import "_ASAsyncTransactionContainer+Private.h" #import #import #import -#import -#import +#import "_ASPendingState.h" +#import "_ASScopeTimer.h" #import #import -#import -#import #import #import #import #import #import #import -#import +#import "ASLayoutElementStylePrivate.h" #import -#import -#import +#import "ASLayoutSpecPrivate.h" #import -#import #import -#import +#import "ASSignpost.h" #import -#import -#import -#import +#import "ASWeakProxy.h" +#import "ASResponderChainEnumerator.h" // Conditionally time these scopes to our debug ivars (only exist in debug/profile builds) #if TIME_DISPLAYNODE_OPS @@ -213,7 +206,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // Subclasses should never override these. Use unused to prevent warnings __unused NSString *classString = NSStringFromClass(self); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(calculatedSize)), @"Subclass %@ must not override calculatedSize method.", classString); + //ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(calculatedSize)), @"Subclass %@ must not override calculatedSize method.", classString); ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(calculatedLayout)), @"Subclass %@ must not override calculatedLayout method.", classString); ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(layoutThatFits:)), @"Subclass %@ must not override layoutThatFits: method. Instead override calculateLayoutThatFits:.", classString); ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(layoutThatFits:parentSize:)), @"Subclass %@ must not override layoutThatFits:parentSize method. Instead override calculateLayoutThatFits:.", classString); @@ -505,7 +498,9 @@ ASSynthesizeLockingMethodsWithMutex(__instanceLock__); ASAssertLocked(__instanceLock__); UIView *view = nil; + bool initializedWithCustomView = false; if (_viewBlock) { + initializedWithCustomView = true; view = _viewBlock(); ASDisplayNodeAssertNotNil(view, @"View block returned nil"); ASDisplayNodeAssert(![view isKindOfClass:[_ASDisplayView class]], @"View block should return a synchronously displayed view"); @@ -525,6 +520,19 @@ ASSynthesizeLockingMethodsWithMutex(__instanceLock__); _flags.canCallSetNeedsDisplayOfLayer = NO; } + if (initializedWithCustomView) { + static dispatch_once_t onceToken; + static IMP defaultMethod = NULL; + dispatch_once(&onceToken, ^{ + defaultMethod = [[UIView class] instanceMethodForSelector:@selector(drawRect:)]; + }); + if ([[view class] instanceMethodForSelector:@selector(drawRect:)] != defaultMethod) { + } else { + _flags.canClearContentsOfLayer = NO; + _flags.canCallSetNeedsDisplayOfLayer = NO; + } + } + // UIActivityIndicator if ([_viewClass isSubclassOfClass:[UIActivityIndicatorView class]] || [_viewClass isSubclassOfClass:[UIVisualEffectView class]]) { @@ -593,7 +601,6 @@ ASSynthesizeLockingMethodsWithMutex(__instanceLock__); ASDisplayNodeAssertMainThread(); ASAssertUnlocked(__instanceLock__); ASDisplayNodeLogEvent(self, @"didLoad"); - as_log_verbose(ASNodeLog(), "didLoad %@", self); TIME_SCOPED(_debugTimeForDidLoad); [self didLoad]; @@ -897,18 +904,6 @@ ASSynthesizeLockingMethodsWithMutex(__instanceLock__); _automaticallyRelayoutOnLayoutMarginsChanges = flag; } -- (void)__setNodeController:(ASNodeController *)controller -{ - // See docs for why we don't lock. - if (controller.shouldInvertStrongReference) { - _strongNodeController = controller; - _weakNodeController = nil; - } else { - _weakNodeController = controller; - _strongNodeController = nil; - } -} - #pragma mark - UIResponder #define HANDLE_NODE_RESPONDER_METHOD(__sel) \ @@ -1060,7 +1055,6 @@ ASSynthesizeLockingMethodsWithMutex(__instanceLock__); // Performing layout on a zero-bounds view often results in frame calculations // with negative sizes after applying margins, which will cause // layoutThatFits: on subnodes to assert. - as_log_debug(OS_LOG_DISABLED, "Warning: No size given for node before node was trying to layout itself: %@. Please provide a frame for the node.", self); return; } @@ -1070,8 +1064,6 @@ ASSynthesizeLockingMethodsWithMutex(__instanceLock__); return; } - as_activity_create_for_scope("-[ASDisplayNode __layout]"); - // This method will confirm that the layout is up to date (and update if needed). // Importantly, it will also APPLY the layout to all of our subnodes if (unless parent is transitioning). l.unlock(); @@ -1109,9 +1101,6 @@ ASSynthesizeLockingMethodsWithMutex(__instanceLock__); restrictedToSize:(ASLayoutElementSize)size relativeToParentSize:(CGSize)parentSize { - as_activity_scope_verbose(as_activity_create("Calculate node layout", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT)); - as_log_verbose(ASLayoutLog(), "Calculating layout for %@ sizeRange %@", self, NSStringFromASSizeRange(constrainedSize)); - #if AS_KDEBUG_ENABLE // We only want one calculateLayout signpost interval per thread. // Currently there is no fallback for profiling i386, since it's not useful. @@ -1124,7 +1113,6 @@ ASSynthesizeLockingMethodsWithMutex(__instanceLock__); ASSizeRange styleAndParentSize = ASLayoutElementSizeResolve(self.style.size, parentSize); const ASSizeRange resolvedRange = ASSizeRangeIntersect(constrainedSize, styleAndParentSize); ASLayout *result = [self calculateLayoutThatFits:resolvedRange]; - as_log_verbose(ASLayoutLog(), "Calculated layout %@", result); #if AS_KDEBUG_ENABLE if (--tls_callDepth == 0) { @@ -1355,7 +1343,6 @@ NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"AS }]; }); - as_log_verbose(ASDisplayLog(), "%s %@", sel_getName(_cmd), node); [renderQueue enqueue:node]; } @@ -1580,29 +1567,6 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) - (void)_setClipCornerLayersVisible:(BOOL)visible { - ASPerformBlockOnMainThread(^{ - ASDisplayNodeAssertMainThread(); - if (visible) { - for (int idx = 0; idx < NUM_CLIP_CORNER_LAYERS; idx++) { - if (_clipCornerLayers[idx] == nil) { - static ASDisplayNodeCornerLayerDelegate *clipCornerLayers; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - clipCornerLayers = [[ASDisplayNodeCornerLayerDelegate alloc] init]; - }); - _clipCornerLayers[idx] = [[CALayer alloc] init]; - _clipCornerLayers[idx].zPosition = 99999; - _clipCornerLayers[idx].delegate = clipCornerLayers; - } - } - [self _updateClipCornerLayerContentsWithRadius:_cornerRadius backgroundColor:self.backgroundColor]; - } else { - for (int idx = 0; idx < NUM_CLIP_CORNER_LAYERS; idx++) { - [_clipCornerLayers[idx] removeFromSuperlayer]; - _clipCornerLayers[idx] = nil; - } - } - }); } - (void)updateCornerRoundingWithType:(ASCornerRoundingType)newRoundingType cornerRadius:(CGFloat)newCornerRadius @@ -2112,8 +2076,6 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { // TODO: Disabled due to PR: https://github.com/TextureGroup/Texture/pull/1204 // ASAssertUnlocked(__instanceLock__); - as_log_verbose(ASNodeLog(), "Insert subnode %@ at index %zd of %@ and remove subnode %@", subnode, subnodeIndex, self, oldSubnode); - if (subnode == nil || subnode == self) { ASDisplayNodeFailAssert(@"Cannot insert a nil subnode or self as subnode"); return; @@ -2550,7 +2512,6 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { // Note: we continue even if supernode is nil to ensure view/layer are removed from hierarchy. if (supernode != nil) { - as_log_verbose(ASNodeLog(), "Remove %@ from supernode %@", self, supernode); } // Clear supernode's reference to us before removing the view from the hierarchy, as _ASDisplayView @@ -2830,7 +2791,6 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { } ASDisplayNodeLogEvent(self, @"setHierarchyState: %@", NSStringFromASHierarchyStateChange(oldState, newState)); - as_log_verbose(ASNodeLog(), "%s%@ %@", sel_getName(_cmd), NSStringFromASHierarchyStateChange(oldState, newState), self); } - (void)willEnterHierarchy @@ -2945,8 +2905,6 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { - (void)recursivelySetInterfaceState:(ASInterfaceState)newInterfaceState { - as_activity_create_for_scope("Recursively set interface state"); - // Instead of each node in the recursion assuming it needs to schedule itself for display, // setInterfaceState: skips this when handling range-managed nodes (our whole subtree has this set). // If our range manager intends for us to be displayed right now, and didn't before, get started! @@ -3107,7 +3065,6 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { // for all cell nodes and it isn't currently meaningful. BOOL measureChangeOnly = ((oldState | newState) == ASInterfaceStateMeasureLayout); if (!measureChangeOnly) { - as_log_verbose(ASNodeLog(), "%s %@ %@", sel_getName(_cmd), NSStringFromASInterfaceStateChange(oldState, newState), self); } ASDisplayNodeLogEvent(self, @"interfaceStateDidChange: %@", NSStringFromASInterfaceStateChange(oldState, newState)); diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode.mm.orig b/submodules/AsyncDisplayKit/Source/ASDisplayNode.mm.orig deleted file mode 100644 index 50510817ab..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode.mm.orig +++ /dev/null @@ -1,3927 +0,0 @@ -// -// ASDisplayNode.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import -#import -#import -#import - -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -// Conditionally time these scopes to our debug ivars (only exist in debug/profile builds) -#if TIME_DISPLAYNODE_OPS - #define TIME_SCOPED(outVar) ASDN::ScopeTimer t(outVar) -#else - #define TIME_SCOPED(outVar) -#endif -// This is trying to merge non-rangeManaged with rangeManaged, so both range-managed and standalone nodes wait before firing their exit-visibility handlers, as UIViewController transitions now do rehosting at both start & end of animation. -// Enable this will mitigate interface updating state when coalescing disabled. -// TODO(wsdwsd0829): Rework enabling code to ensure that interface state behavior is not altered when ASCATransactionQueue is disabled. -#define ENABLE_NEW_EXIT_HIERARCHY_BEHAVIOR 0 - -static ASDisplayNodeNonFatalErrorBlock _nonFatalErrorBlock = nil; -NSInteger const ASDefaultDrawingPriority = ASDefaultTransactionPriority; - -// Forward declare CALayerDelegate protocol as the iOS 10 SDK moves CALayerDelegate from an informal delegate to a protocol. -// We have to forward declare the protocol as this place otherwise it will not compile compiling with an Base SDK < iOS 10 -@protocol CALayerDelegate; - -@interface ASDisplayNode () -/** - * See ASDisplayNodeInternal.h for ivars - */ - -@end - -@implementation ASDisplayNode - -@dynamic layoutElementType; - -@synthesize threadSafeBounds = _threadSafeBounds; - -static std::atomic_bool storesUnflattenedLayouts = ATOMIC_VAR_INIT(NO); - -BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector) -{ - return ASSubclassOverridesSelector([ASDisplayNode class], subclass, selector); -} - -// For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - we have to be sure to set certain properties -// like setFrame: and setBackgroundColor: directly to the UIView and not apply it to the layer only. -BOOL ASDisplayNodeNeedsSpecialPropertiesHandling(BOOL isSynchronous, BOOL isLayerBacked) -{ - return isSynchronous && !isLayerBacked; -} - -_ASPendingState *ASDisplayNodeGetPendingState(ASDisplayNode *node) -{ - ASLockScope(node); - _ASPendingState *result = node->_pendingViewState; - if (result == nil) { - result = [[_ASPendingState alloc] init]; - node->_pendingViewState = result; - } - return result; -} - -/** - * Returns ASDisplayNodeFlags for the given class/instance. instance MAY BE NIL. - * - * @param c the class, required - * @param instance the instance, which may be nil. (If so, the class is inspected instead) - * @remarks The instance value is used only if we suspect the class may be dynamic (because it overloads - * +respondsToSelector: or -respondsToSelector.) In that case we use our "slow path", calling this - * method on each -init and passing the instance value. While this may seem like an unlikely scenario, - * it turns our our own internal tests use a dynamic class, so it's worth capturing this edge case. - * - * @return ASDisplayNode flags. - */ -static struct ASDisplayNodeFlags GetASDisplayNodeFlags(Class c, ASDisplayNode *instance) -{ - ASDisplayNodeCAssertNotNil(c, @"class is required"); - - struct ASDisplayNodeFlags flags = {0}; - - flags.isInHierarchy = NO; - flags.displaysAsynchronously = YES; - flags.shouldAnimateSizeChanges = YES; - flags.implementsDrawRect = ([c respondsToSelector:@selector(drawRect:withParameters:isCancelled:isRasterizing:)] ? 1 : 0); - flags.implementsImageDisplay = ([c respondsToSelector:@selector(displayWithParameters:isCancelled:)] ? 1 : 0); - if (instance) { - flags.implementsDrawParameters = ([instance respondsToSelector:@selector(drawParametersForAsyncLayer:)] ? 1 : 0); - } else { - flags.implementsDrawParameters = ([c instancesRespondToSelector:@selector(drawParametersForAsyncLayer:)] ? 1 : 0); - } - - - return flags; -} - -/** - * Returns ASDisplayNodeMethodOverrides for the given class - * - * @param c the class, required. - * - * @return ASDisplayNodeMethodOverrides. - */ -static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) -{ - ASDisplayNodeCAssertNotNil(c, @"class is required"); - - ASDisplayNodeMethodOverrides overrides = ASDisplayNodeMethodOverrideNone; - - // Handling touches - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(touchesBegan:withEvent:))) { - overrides |= ASDisplayNodeMethodOverrideTouchesBegan; - } - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(touchesMoved:withEvent:))) { - overrides |= ASDisplayNodeMethodOverrideTouchesMoved; - } - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(touchesCancelled:withEvent:))) { - overrides |= ASDisplayNodeMethodOverrideTouchesCancelled; - } - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(touchesEnded:withEvent:))) { - overrides |= ASDisplayNodeMethodOverrideTouchesEnded; - } - - // Responder chain - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(canBecomeFirstResponder))) { - overrides |= ASDisplayNodeMethodOverrideCanBecomeFirstResponder; - } - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(becomeFirstResponder))) { - overrides |= ASDisplayNodeMethodOverrideBecomeFirstResponder; - } - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(canResignFirstResponder))) { - overrides |= ASDisplayNodeMethodOverrideCanResignFirstResponder; - } - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(resignFirstResponder))) { - overrides |= ASDisplayNodeMethodOverrideResignFirstResponder; - } - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(isFirstResponder))) { - overrides |= ASDisplayNodeMethodOverrideIsFirstResponder; - } - - // Layout related methods - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(layoutSpecThatFits:))) { - overrides |= ASDisplayNodeMethodOverrideLayoutSpecThatFits; - } - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(calculateLayoutThatFits:)) || - ASDisplayNodeSubclassOverridesSelector(c, @selector(calculateLayoutThatFits: - restrictedToSize: - relativeToParentSize:))) { - overrides |= ASDisplayNodeMethodOverrideCalcLayoutThatFits; - } - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(calculateSizeThatFits:))) { - overrides |= ASDisplayNodeMethodOverrideCalcSizeThatFits; - } - - return overrides; -} - -+ (void)initialize -{ -#if ASDISPLAYNODE_ASSERTIONS_ENABLED - if (self != [ASDisplayNode class]) { - - // Subclasses should never override these. Use unused to prevent warnings - __unused NSString *classString = NSStringFromClass(self); - - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(calculatedSize)), @"Subclass %@ must not override calculatedSize method.", classString); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(calculatedLayout)), @"Subclass %@ must not override calculatedLayout method.", classString); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(layoutThatFits:)), @"Subclass %@ must not override layoutThatFits: method. Instead override calculateLayoutThatFits:.", classString); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(layoutThatFits:parentSize:)), @"Subclass %@ must not override layoutThatFits:parentSize method. Instead override calculateLayoutThatFits:.", classString); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(recursivelyClearContents)), @"Subclass %@ must not override recursivelyClearContents method.", classString); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(recursivelyClearPreloadedData)), @"Subclass %@ must not override recursivelyClearFetchedData method.", classString); - } else { - // Check if subnodes where modified during the creation of the layout - __block IMP originalLayoutSpecThatFitsIMP = ASReplaceMethodWithBlock(self, @selector(_locked_layoutElementThatFits:), ^(ASDisplayNode *_self, ASSizeRange sizeRange) { - NSArray *oldSubnodes = _self.subnodes; - ASLayoutSpec *layoutElement = ((ASLayoutSpec *( *)(id, SEL, ASSizeRange))originalLayoutSpecThatFitsIMP)(_self, @selector(_locked_layoutElementThatFits:), sizeRange); - NSArray *subnodes = _self.subnodes; - ASDisplayNodeAssert(oldSubnodes.count == subnodes.count, @"Adding or removing nodes in layoutSpecBlock or layoutSpecThatFits: is not allowed and can cause unexpected behavior."); - for (NSInteger i = 0; i < oldSubnodes.count; i++) { - ASDisplayNodeAssert(oldSubnodes[i] == subnodes[i], @"Adding or removing nodes in layoutSpecBlock or layoutSpecThatFits: is not allowed and can cause unexpected behavior."); - } - return layoutElement; - }); - } -#endif - - // Below we are pre-calculating values per-class and dynamically adding a method (_staticInitialize) to populate these values - // when each instance is constructed. These values don't change for each class, so there is significant performance benefit - // in doing it here. +initialize is guaranteed to be called before any instance method so it is safe to add this method here. - // Note that we take care to detect if the class overrides +respondsToSelector: or -respondsToSelector and take the slow path - // (recalculating for each instance) to make sure we are always correct. - - BOOL classOverridesRespondsToSelector = ASSubclassOverridesClassSelector([NSObject class], self, @selector(respondsToSelector:)); - BOOL instancesOverrideRespondsToSelector = ASSubclassOverridesSelector([NSObject class], self, @selector(respondsToSelector:)); - struct ASDisplayNodeFlags flags = GetASDisplayNodeFlags(self, nil); - ASDisplayNodeMethodOverrides methodOverrides = GetASDisplayNodeMethodOverrides(self); - - __unused Class initializeSelf = self; - - IMP staticInitialize = imp_implementationWithBlock(^(ASDisplayNode *node) { - ASDisplayNodeAssert(node.class == initializeSelf, @"Node class %@ does not have a matching _staticInitialize method; check to ensure [super initialize] is called within any custom +initialize implementations! Overridden methods will not be called unless they are also implemented by superclass %@", node.class, initializeSelf); - node->_flags = (classOverridesRespondsToSelector || instancesOverrideRespondsToSelector) ? GetASDisplayNodeFlags(node.class, node) : flags; - node->_methodOverrides = (classOverridesRespondsToSelector) ? GetASDisplayNodeMethodOverrides(node.class) : methodOverrides; - }); - - class_replaceMethod(self, @selector(_staticInitialize), staticInitialize, "v:@"); -} - -#if !AS_INITIALIZE_FRAMEWORK_MANUALLY -+ (void)load -{ - ASInitializeFrameworkMainThread(); -} -#endif - -+ (Class)viewClass -{ - return [_ASDisplayView class]; -} - -+ (Class)layerClass -{ - return [_ASDisplayLayer class]; -} - -#pragma mark - Lifecycle - -- (void)_staticInitialize -{ - ASDisplayNodeAssert(NO, @"_staticInitialize must be overridden"); -} - -- (void)_initializeInstance -{ - [self _staticInitialize]; - -#if ASEVENTLOG_ENABLE - _eventLog = [[ASEventLog alloc] initWithObject:self]; -#endif - - _viewClass = [self.class viewClass]; - _layerClass = [self.class layerClass]; - BOOL isSynchronous = ![_viewClass isSubclassOfClass:[_ASDisplayView class]] - || ![_layerClass isSubclassOfClass:[_ASDisplayLayer class]]; - setFlag(Synchronous, isSynchronous); - - - _contentsScaleForDisplay = ASScreenScale(); - _drawingPriority = ASDefaultDrawingPriority; - - _primitiveTraitCollection = ASPrimitiveTraitCollectionMakeDefault(); - - _layoutVersion = 1; - - _defaultLayoutTransitionDuration = 0.2; - _defaultLayoutTransitionDelay = 0.0; - _defaultLayoutTransitionOptions = UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionTransitionNone; - - _flags.canClearContentsOfLayer = YES; - _flags.canCallSetNeedsDisplayOfLayer = YES; - - _fallbackSafeAreaInsets = UIEdgeInsetsZero; - _fallbackInsetsLayoutMarginsFromSafeArea = YES; - _isViewControllerRoot = NO; - - _automaticallyRelayoutOnSafeAreaChanges = NO; - _automaticallyRelayoutOnLayoutMarginsChanges = NO; - - ASDisplayNodeLogEvent(self, @"init"); -} - -- (instancetype)init -{ - if (!(self = [super init])) - return nil; - - [self _initializeInstance]; - - return self; -} - -- (instancetype)initWithViewClass:(Class)viewClass -{ - if (!(self = [self init])) - return nil; - - ASDisplayNodeAssert([viewClass isSubclassOfClass:[UIView class]], @"should initialize with a subclass of UIView"); - - _viewClass = viewClass; - setFlag(Synchronous, ![viewClass isSubclassOfClass:[_ASDisplayView class]]); - - return self; -} - -- (instancetype)initWithLayerClass:(Class)layerClass -{ - if (!(self = [self init])) { - return nil; - } - - ASDisplayNodeAssert([layerClass isSubclassOfClass:[CALayer class]], @"should initialize with a subclass of CALayer"); - - _layerClass = layerClass; - _flags.layerBacked = YES; - setFlag(Synchronous, ![layerClass isSubclassOfClass:[_ASDisplayLayer class]]); - - return self; -} - -- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock -{ - return [self initWithViewBlock:viewBlock didLoadBlock:nil]; -} - -- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock -{ - if (!(self = [self init])) { - return nil; - } - - [self setViewBlock:viewBlock]; - if (didLoadBlock != nil) { - [self onDidLoad:didLoadBlock]; - } - - return self; -} - -- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)layerBlock -{ - return [self initWithLayerBlock:layerBlock didLoadBlock:nil]; -} - -- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)layerBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock -{ - if (!(self = [self init])) { - return nil; - } - - [self setLayerBlock:layerBlock]; - if (didLoadBlock != nil) { - [self onDidLoad:didLoadBlock]; - } - - return self; -} - -ASSynthesizeLockingMethodsWithMutex(__instanceLock__); - -- (void)setViewBlock:(ASDisplayNodeViewBlock)viewBlock -{ - ASDisplayNodeAssertFalse(self.nodeLoaded); - ASDisplayNodeAssertNotNil(viewBlock, @"should initialize with a valid block that returns a UIView"); - - _viewBlock = viewBlock; - setFlag(Synchronous, YES); -} - -- (void)setLayerBlock:(ASDisplayNodeLayerBlock)layerBlock -{ - ASDisplayNodeAssertFalse(self.nodeLoaded); - ASDisplayNodeAssertNotNil(layerBlock, @"should initialize with a valid block that returns a CALayer"); - - _layerBlock = layerBlock; - _flags.layerBacked = YES; - setFlag(Synchronous, YES); -} - -- (ASDisplayNodeMethodOverrides)methodOverrides -{ - return _methodOverrides; -} - -- (void)onDidLoad:(ASDisplayNodeDidLoadBlock)body -{ - ASDN::MutexLocker l(__instanceLock__); - - if ([self _locked_isNodeLoaded]) { - ASDisplayNodeAssertThreadAffinity(self); - ASDN::MutexUnlocker l(__instanceLock__); - body(self); - } else if (_onDidLoadBlocks == nil) { - _onDidLoadBlocks = [NSMutableArray arrayWithObject:body]; - } else { - [_onDidLoadBlocks addObject:body]; - } -} - -- (void)dealloc -{ - _flags.isDeallocating = YES; - - // Synchronous nodes may not be able to call the hierarchy notifications, so only enforce for regular nodes. - ASDisplayNodeAssert(checkFlag(Synchronous) || !ASInterfaceStateIncludesVisible(_interfaceState), @"Node should always be marked invisible before deallocating. Node: %@", self); - - self.asyncLayer.asyncDelegate = nil; - _view.asyncdisplaykit_node = nil; - _layer.asyncdisplaykit_node = nil; - - // Remove any subnodes so they lose their connection to the now deallocated parent. This can happen - // because subnodes do not retain their supernode, but subnodes can legitimately remain alive if another - // thing outside the view hierarchy system (e.g. async display, controller code, etc). keeps a retained - // reference to subnodes. - - for (ASDisplayNode *subnode in _subnodes) - [subnode _setSupernode:nil]; - - [self scheduleIvarsForMainThreadDeallocation]; - - // TODO: Remove this? If supernode isn't already nil, this method isn't dealloc-safe anyway. - [self _setSupernode:nil]; -} - -#pragma mark - Loading - -- (BOOL)_locked_shouldLoadViewOrLayer -{ - ASAssertLocked(__instanceLock__); - return !_flags.isDeallocating && !(_hierarchyState & ASHierarchyStateRasterized); -} - -- (UIView *)_locked_viewToLoad -{ - ASAssertLocked(__instanceLock__); - - UIView *view = nil; - if (_viewBlock) { - view = _viewBlock(); - ASDisplayNodeAssertNotNil(view, @"View block returned nil"); - ASDisplayNodeAssert(![view isKindOfClass:[_ASDisplayView class]], @"View block should return a synchronously displayed view"); - _viewBlock = nil; - _viewClass = [view class]; - } else { - view = [[_viewClass alloc] init]; - } - - // Special handling of wrapping UIKit components - if (checkFlag(Synchronous)) { - [self checkResponderCompatibility]; - - // UIImageView layers. More details on the flags - if ([_viewClass isSubclassOfClass:[UIImageView class]]) { - _flags.canClearContentsOfLayer = NO; - _flags.canCallSetNeedsDisplayOfLayer = NO; - } - - // UIActivityIndicator - if ([_viewClass isSubclassOfClass:[UIActivityIndicatorView class]] - || [_viewClass isSubclassOfClass:[UIVisualEffectView class]]) { - self.opaque = NO; - } - - // CAEAGLLayer - if([[view.layer class] isSubclassOfClass:[CAEAGLLayer class]]){ - _flags.canClearContentsOfLayer = NO; - } - } - - return view; -} - -- (CALayer *)_locked_layerToLoad -{ - ASAssertLocked(__instanceLock__); - ASDisplayNodeAssert(_flags.layerBacked, @"_layerToLoad is only for layer-backed nodes"); - - CALayer *layer = nil; - if (_layerBlock) { - layer = _layerBlock(); - ASDisplayNodeAssertNotNil(layer, @"Layer block returned nil"); - ASDisplayNodeAssert(![layer isKindOfClass:[_ASDisplayLayer class]], @"Layer block should return a synchronously displayed layer"); - _layerBlock = nil; - _layerClass = [layer class]; - } else { - layer = [[_layerClass alloc] init]; - } - - return layer; -} - -- (void)_locked_loadViewOrLayer -{ - ASAssertLocked(__instanceLock__); - - if (_flags.layerBacked) { - TIME_SCOPED(_debugTimeToCreateView); - _layer = [self _locked_layerToLoad]; - static int ASLayerDelegateAssociationKey; - - /** - * CALayer's .delegate property is documented to be weak, but the implementation is actually assign. - * Because our layer may survive longer than the node (e.g. if someone else retains it, or if the node - * begins deallocation on a background thread and it waiting for the -dealloc call to reach main), the only - * way to avoid a dangling pointer is to use a weak proxy. - */ - ASWeakProxy *instance = [ASWeakProxy weakProxyWithTarget:self]; - _layer.delegate = (id)instance; - objc_setAssociatedObject(_layer, &ASLayerDelegateAssociationKey, instance, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - } else { - TIME_SCOPED(_debugTimeToCreateView); - _view = [self _locked_viewToLoad]; - _view.asyncdisplaykit_node = self; - _layer = _view.layer; - } - _layer.asyncdisplaykit_node = self; - - self._locked_asyncLayer.asyncDelegate = self; -} - -- (void)_didLoad -{ - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - ASDisplayNodeLogEvent(self, @"didLoad"); - as_log_verbose(ASNodeLog(), "didLoad %@", self); - TIME_SCOPED(_debugTimeForDidLoad); - - [self didLoad]; - - __instanceLock__.lock(); - let onDidLoadBlocks = ASTransferStrong(_onDidLoadBlocks); - __instanceLock__.unlock(); - - for (ASDisplayNodeDidLoadBlock block in onDidLoadBlocks) { - block(self); - } - [self enumerateInterfaceStateDelegates:^(id del) { - [del nodeDidLoad]; - }]; -} - -- (void)didLoad -{ - ASDisplayNodeAssertMainThread(); - - // Subclass hook -} - -- (BOOL)isNodeLoaded -{ - if (ASDisplayNodeThreadIsMain()) { - // Because the view and layer can only be created and destroyed on Main, that is also the only thread - // where the state of this property can change. As an optimization, we can avoid locking. - return _loaded(self); - } else { - ASDN::MutexLocker l(__instanceLock__); - return [self _locked_isNodeLoaded]; - } -} - -- (BOOL)_locked_isNodeLoaded -{ - ASAssertLocked(__instanceLock__); - return _loaded(self); -} - -#pragma mark - Misc Setter / Getter - -- (UIView *)view -{ - ASDN::MutexLocker l(__instanceLock__); - - ASDisplayNodeAssert(!_flags.layerBacked, @"Call to -view undefined on layer-backed nodes"); - BOOL isLayerBacked = _flags.layerBacked; - if (isLayerBacked) { - return nil; - } - - if (_view != nil) { - return _view; - } - - if (![self _locked_shouldLoadViewOrLayer]) { - return nil; - } - - // Loading a view needs to happen on the main thread - ASDisplayNodeAssertMainThread(); - [self _locked_loadViewOrLayer]; - - // FIXME: Ideally we'd call this as soon as the node receives -setNeedsLayout - // but automatic subnode management would require us to modify the node tree - // in the background on a loaded node, which isn't currently supported. - if (_pendingViewState.hasSetNeedsLayout) { - // Need to unlock before calling setNeedsLayout to avoid deadlocks. - // MutexUnlocker will re-lock at the end of scope. - ASDN::MutexUnlocker u(__instanceLock__); - [self __setNeedsLayout]; - } - - [self _locked_applyPendingStateToViewOrLayer]; - - { - // The following methods should not be called with a lock - ASDN::MutexUnlocker u(__instanceLock__); - - // No need for the lock as accessing the subviews or layers are always happening on main - [self _addSubnodeViewsAndLayers]; - - // A subclass hook should never be called with a lock - [self _didLoad]; - } - - return _view; -} - -- (CALayer *)layer -{ - ASDN::MutexLocker l(__instanceLock__); - if (_layer != nil) { - return _layer; - } - - if (![self _locked_shouldLoadViewOrLayer]) { - return nil; - } - - // Loading a layer needs to happen on the main thread - ASDisplayNodeAssertMainThread(); - [self _locked_loadViewOrLayer]; - - // FIXME: Ideally we'd call this as soon as the node receives -setNeedsLayout - // but automatic subnode management would require us to modify the node tree - // in the background on a loaded node, which isn't currently supported. - if (_pendingViewState.hasSetNeedsLayout) { - // Need to unlock before calling setNeedsLayout to avoid deadlocks. - // MutexUnlocker will re-lock at the end of scope. - ASDN::MutexUnlocker u(__instanceLock__); - [self __setNeedsLayout]; - } - - [self _locked_applyPendingStateToViewOrLayer]; - - { - // The following methods should not be called with a lock - ASDN::MutexUnlocker u(__instanceLock__); - - // No need for the lock as accessing the subviews or layers are always happening on main - [self _addSubnodeViewsAndLayers]; - - // A subclass hook should never be called with a lock - [self _didLoad]; - } - - return _layer; -} - -// Returns nil if the layer is not an _ASDisplayLayer; will not create the layer if nil. -- (_ASDisplayLayer *)asyncLayer -{ - ASDN::MutexLocker l(__instanceLock__); - return [self _locked_asyncLayer]; -} - -- (_ASDisplayLayer *)_locked_asyncLayer -{ - ASAssertLocked(__instanceLock__); - return [_layer isKindOfClass:[_ASDisplayLayer class]] ? (_ASDisplayLayer *)_layer : nil; -} - -- (BOOL)isSynchronous -{ - return checkFlag(Synchronous); -} - -- (void)setLayerBacked:(BOOL)isLayerBacked -{ - // Only call this if assertions are enabled – it could be expensive. - ASDisplayNodeAssert(!isLayerBacked || self.supportsLayerBacking, @"Node %@ does not support layer backing.", self); - - ASDN::MutexLocker l(__instanceLock__); - if (_flags.layerBacked == isLayerBacked) { - return; - } - - if ([self _locked_isNodeLoaded]) { - ASDisplayNodeFailAssert(@"Cannot change layerBacked after view/layer has loaded."); - return; - } - - _flags.layerBacked = isLayerBacked; -} - -- (BOOL)isLayerBacked -{ - ASDN::MutexLocker l(__instanceLock__); - return _flags.layerBacked; -} - -- (BOOL)supportsLayerBacking -{ - ASDN::MutexLocker l(__instanceLock__); - return !checkFlag(Synchronous) && !_flags.viewEverHadAGestureRecognizerAttached && _viewClass == [_ASDisplayView class] && _layerClass == [_ASDisplayLayer class]; -} - -- (BOOL)shouldAnimateSizeChanges -{ - ASDN::MutexLocker l(__instanceLock__); - return _flags.shouldAnimateSizeChanges; -} - -- (void)setShouldAnimateSizeChanges:(BOOL)shouldAnimateSizeChanges -{ - ASDN::MutexLocker l(__instanceLock__); - _flags.shouldAnimateSizeChanges = shouldAnimateSizeChanges; -} - -- (CGRect)threadSafeBounds -{ - ASDN::MutexLocker l(__instanceLock__); - return [self _locked_threadSafeBounds]; -} - -- (CGRect)_locked_threadSafeBounds -{ - ASAssertLocked(__instanceLock__); - return _threadSafeBounds; -} - -- (void)setThreadSafeBounds:(CGRect)newBounds -{ - ASDN::MutexLocker l(__instanceLock__); - _threadSafeBounds = newBounds; -} - -- (void)nodeViewDidAddGestureRecognizer -{ - ASDN::MutexLocker l(__instanceLock__); - _flags.viewEverHadAGestureRecognizerAttached = YES; -} - -- (UIEdgeInsets)fallbackSafeAreaInsets -{ - ASDN::MutexLocker l(__instanceLock__); - return _fallbackSafeAreaInsets; -} - -- (void)setFallbackSafeAreaInsets:(UIEdgeInsets)insets -{ - BOOL needsManualUpdate; - BOOL updatesLayoutMargins; - - { - ASDN::MutexLocker l(__instanceLock__); - ASDisplayNodeAssertThreadAffinity(self); - - if (UIEdgeInsetsEqualToEdgeInsets(insets, _fallbackSafeAreaInsets)) { - return; - } - - _fallbackSafeAreaInsets = insets; - needsManualUpdate = !AS_AT_LEAST_IOS11 || _flags.layerBacked; - updatesLayoutMargins = needsManualUpdate && [self _locked_insetsLayoutMarginsFromSafeArea]; - } - - if (needsManualUpdate) { - [self safeAreaInsetsDidChange]; - } - - if (updatesLayoutMargins) { - [self layoutMarginsDidChange]; - } -} - -- (void)_fallbackUpdateSafeAreaOnChildren -{ - ASDisplayNodeAssertThreadAffinity(self); - - UIEdgeInsets insets = self.safeAreaInsets; - CGRect bounds = self.bounds; - - for (ASDisplayNode *child in self.subnodes) { - if (AS_AT_LEAST_IOS11 && !child.layerBacked) { - // In iOS 11 view-backed nodes already know what their safe area is. - continue; - } - - if (child.viewControllerRoot) { - // Its safe area is controlled by a view controller. Don't override it. - continue; - } - - CGRect childFrame = child.frame; - UIEdgeInsets childInsets = UIEdgeInsetsMake(MAX(insets.top - (CGRectGetMinY(childFrame) - CGRectGetMinY(bounds)), 0), - MAX(insets.left - (CGRectGetMinX(childFrame) - CGRectGetMinX(bounds)), 0), - MAX(insets.bottom - (CGRectGetMaxY(bounds) - CGRectGetMaxY(childFrame)), 0), - MAX(insets.right - (CGRectGetMaxX(bounds) - CGRectGetMaxX(childFrame)), 0)); - - child.fallbackSafeAreaInsets = childInsets; - } -} - -- (BOOL)isViewControllerRoot -{ - ASDN::MutexLocker l(__instanceLock__); - return _isViewControllerRoot; -} - -- (void)setViewControllerRoot:(BOOL)flag -{ - ASDN::MutexLocker l(__instanceLock__); - _isViewControllerRoot = flag; -} - -- (BOOL)automaticallyRelayoutOnSafeAreaChanges -{ - ASDN::MutexLocker l(__instanceLock__); - return _automaticallyRelayoutOnSafeAreaChanges; -} - -- (void)setAutomaticallyRelayoutOnSafeAreaChanges:(BOOL)flag -{ - ASDN::MutexLocker l(__instanceLock__); - _automaticallyRelayoutOnSafeAreaChanges = flag; -} - -- (BOOL)automaticallyRelayoutOnLayoutMarginsChanges -{ - ASDN::MutexLocker l(__instanceLock__); - return _automaticallyRelayoutOnLayoutMarginsChanges; -} - -- (void)setAutomaticallyRelayoutOnLayoutMarginsChanges:(BOOL)flag -{ - ASDN::MutexLocker l(__instanceLock__); - _automaticallyRelayoutOnLayoutMarginsChanges = flag; -} - -- (void)__setNodeController:(ASNodeController *)controller -{ - // See docs for why we don't lock. - if (controller.shouldInvertStrongReference) { - _strongNodeController = controller; - _weakNodeController = nil; - } else { - _weakNodeController = controller; - _strongNodeController = nil; - } -} - -#pragma mark - UIResponder - -#define HANDLE_NODE_RESPONDER_METHOD(__sel) \ - /* All responder methods should be called on the main thread */ \ - ASDisplayNodeAssertMainThread(); \ - if (checkFlag(Synchronous)) { \ - /* If the view is not a _ASDisplayView subclass (Synchronous) just call through to the view as we - expect it's a non _ASDisplayView subclass that will respond */ \ - return [_view __sel]; \ - } else { \ - if (ASSubclassOverridesSelector([_ASDisplayView class], _viewClass, @selector(__sel))) { \ - /* If the subclass overwrites canBecomeFirstResponder just call through - to it as we expect it will handle it */ \ - return [_view __sel]; \ - } else { \ - /* Call through to _ASDisplayView's superclass to get it handled */ \ - return [(_ASDisplayView *)_view __##__sel]; \ - } \ - } \ - -- (void)checkResponderCompatibility -{ -#if ASDISPLAYNODE_ASSERTIONS_ENABLED - // There are certain cases we cannot handle and are not supported: - // 1. If the _view class is not a subclass of _ASDisplayView - if (checkFlag(Synchronous)) { - // 2. At least one UIResponder methods are overwritten in the node subclass - NSString *message = @"Overwritting %@ and having a backing view that is not an _ASDisplayView is not supported."; - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self.class, @selector(canBecomeFirstResponder)), ([NSString stringWithFormat:message, @"canBecomeFirstResponder"])); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self.class, @selector(becomeFirstResponder)), ([NSString stringWithFormat:message, @"becomeFirstResponder"])); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self.class, @selector(canResignFirstResponder)), ([NSString stringWithFormat:message, @"canResignFirstResponder"])); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self.class, @selector(resignFirstResponder)), ([NSString stringWithFormat:message, @"resignFirstResponder"])); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self.class, @selector(isFirstResponder)), ([NSString stringWithFormat:message, @"isFirstResponder"])); - } -#endif -} - -- (BOOL)__canBecomeFirstResponder -{ - if (_view == nil) { - // By default we return NO if not view is created yet - return NO; - } - - HANDLE_NODE_RESPONDER_METHOD(canBecomeFirstResponder); -} - -- (BOOL)__becomeFirstResponder -{ - // Note: This implicitly loads the view if it hasn't been loaded yet. - [self view]; - - if (![self canBecomeFirstResponder]) { - return NO; - } - - HANDLE_NODE_RESPONDER_METHOD(becomeFirstResponder); -} - -- (BOOL)__canResignFirstResponder -{ - if (_view == nil) { - // By default we return YES if no view is created yet - return YES; - } - - HANDLE_NODE_RESPONDER_METHOD(canResignFirstResponder); -} - -- (BOOL)__resignFirstResponder -{ - // Note: This implicitly loads the view if it hasn't been loaded yet. - [self view]; - - if (![self canResignFirstResponder]) { - return NO; - } - - HANDLE_NODE_RESPONDER_METHOD(resignFirstResponder); -} - -- (BOOL)__isFirstResponder -{ - if (_view == nil) { - // If no view is created yet we can just return NO as it's unlikely it's the first responder - return NO; - } - - HANDLE_NODE_RESPONDER_METHOD(isFirstResponder); -} - -#pragma mark - -- (NSString *)debugName -{ - ASDN::MutexLocker l(__instanceLock__); - return _debugName; -} - -- (void)setDebugName:(NSString *)debugName -{ - ASDN::MutexLocker l(__instanceLock__); - if (!ASObjectIsEqual(_debugName, debugName)) { - _debugName = [debugName copy]; - } -} - -#pragma mark - Layout - -// At most a layoutSpecBlock or one of the three layout methods is overridden -#define __ASDisplayNodeCheckForLayoutMethodOverrides \ - ASDisplayNodeAssert(_layoutSpecBlock != NULL || \ - ((ASDisplayNodeSubclassOverridesSelector(self.class, @selector(calculateSizeThatFits:)) ? 1 : 0) \ - + (ASDisplayNodeSubclassOverridesSelector(self.class, @selector(layoutSpecThatFits:)) ? 1 : 0) \ - + (ASDisplayNodeSubclassOverridesSelector(self.class, @selector(calculateLayoutThatFits:)) ? 1 : 0)) <= 1, \ - @"Subclass %@ must at least provide a layoutSpecBlock or override at most one of the three layout methods: calculateLayoutThatFits:, layoutSpecThatFits:, or calculateSizeThatFits:", NSStringFromClass(self.class)) - - -#pragma mark - -- (BOOL)canLayoutAsynchronous -{ - return !self.isNodeLoaded; -} - -#pragma mark Layout Pass - -- (void)__setNeedsLayout -{ - [self invalidateCalculatedLayout]; -} - -- (void)invalidateCalculatedLayout -{ - ASDN::MutexLocker l(__instanceLock__); - - _layoutVersion++; - - _unflattenedLayout = nil; - -#if YOGA - [self invalidateCalculatedYogaLayout]; -#endif -} - -- (void)__layout -{ - ASDisplayNodeAssertThreadAffinity(self); - ASAssertUnlocked(__instanceLock__); - - BOOL loaded = NO; - { - ASDN::MutexLocker l(__instanceLock__); - loaded = [self _locked_isNodeLoaded]; - CGRect bounds = _threadSafeBounds; - - if (CGRectEqualToRect(bounds, CGRectZero)) { - // Performing layout on a zero-bounds view often results in frame calculations - // with negative sizes after applying margins, which will cause - // layoutThatFits: on subnodes to assert. - as_log_debug(OS_LOG_DISABLED, "Warning: No size given for node before node was trying to layout itself: %@. Please provide a frame for the node.", self); - return; - } - - // If a current layout transition is in progress there is no need to do a measurement and layout pass in here as - // this is supposed to happen within the layout transition process - if (_transitionID != ASLayoutElementContextInvalidTransitionID) { - return; - } - - as_activity_create_for_scope("-[ASDisplayNode __layout]"); - - // This method will confirm that the layout is up to date (and update if needed). - // Importantly, it will also APPLY the layout to all of our subnodes if (unless parent is transitioning). - { - ASDN::MutexUnlocker u(__instanceLock__); - [self _u_measureNodeWithBoundsIfNecessary:bounds]; - } - - [self _locked_layoutPlaceholderIfNecessary]; - } - - [self _layoutSublayouts]; - - // Per API contract, `-layout` and `-layoutDidFinish` are called only if the node is loaded. - if (loaded) { - ASPerformBlockOnMainThread(^{ - [self layout]; - [self _layoutClipCornersIfNeeded]; - [self layoutDidFinish]; - }); - } - - [self _fallbackUpdateSafeAreaOnChildren]; -} - -- (void)layoutDidFinish -{ - // Hook for subclasses - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - ASDisplayNodeAssertTrue(self.isNodeLoaded); -} - -#pragma mark Calculation - -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize - restrictedToSize:(ASLayoutElementSize)size - relativeToParentSize:(CGSize)parentSize -{ -<<<<<<< HEAD - // We only want one calculateLayout signpost interval per thread. -#ifndef MINIMAL_ASDK - static _Thread_local NSInteger tls_callDepth; -======= ->>>>>>> 565da7d4935740d12fc204aa061faf093831da1e - as_activity_scope_verbose(as_activity_create("Calculate node layout", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT)); - as_log_verbose(ASLayoutLog(), "Calculating layout for %@ sizeRange %@", self, NSStringFromASSizeRange(constrainedSize)); - -#if AS_KDEBUG_ENABLE - // We only want one calculateLayout signpost interval per thread. - // Currently there is no fallback for profiling i386, since it's not useful. - static _Thread_local NSInteger tls_callDepth; - if (tls_callDepth++ == 0) { - ASSignpostStart(ASSignpostCalculateLayout); - } -#endif - - ASSizeRange styleAndParentSize = ASLayoutElementSizeResolve(self.style.size, parentSize); - const ASSizeRange resolvedRange = ASSizeRangeIntersect(constrainedSize, styleAndParentSize); - ASLayout *result = [self calculateLayoutThatFits:resolvedRange]; -#ifndef MINIMAL_ASDK - as_log_verbose(ASLayoutLog(), "Calculated layout %@", result); - -#if AS_KDEBUG_ENABLE - if (--tls_callDepth == 0) { - ASSignpostEnd(ASSignpostCalculateLayout); - } -#endif -<<<<<<< HEAD -======= - ->>>>>>> 565da7d4935740d12fc204aa061faf093831da1e - return result; -} - -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize -{ - __ASDisplayNodeCheckForLayoutMethodOverrides; - - ASDN::MutexLocker l(__instanceLock__); - -#if YOGA - // There are several cases where Yoga could arrive here: - // - This node is not in a Yoga tree: it has neither a yogaParent nor yogaChildren. - // - This node is a Yoga tree root: it has no yogaParent, but has yogaChildren. - // - This node is a Yoga tree node: it has both a yogaParent and yogaChildren. - // - This node is a Yoga tree leaf: it has a yogaParent, but no yogaChidlren. - YGNodeRef yogaNode = _style.yogaNode; - BOOL hasYogaParent = (_yogaParent != nil); - BOOL hasYogaChildren = (_yogaChildren.count > 0); - BOOL usesYoga = (yogaNode != NULL && (hasYogaParent || hasYogaChildren)); - if (usesYoga) { - // This node has some connection to a Yoga tree. - if ([self shouldHaveYogaMeasureFunc] == NO) { - // If we're a yoga root, tree node, or leaf with no measure func (e.g. spacer), then - // initiate a new Yoga calculation pass from root. - ASDN::MutexUnlocker ul(__instanceLock__); - as_activity_create_for_scope("Yoga layout calculation"); - if (self.yogaLayoutInProgress == NO) { - ASYogaLog("Calculating yoga layout from root %@, %@", self, NSStringFromASSizeRange(constrainedSize)); - [self calculateLayoutFromYogaRoot:constrainedSize]; - } else { - ASYogaLog("Reusing existing yoga layout %@", _yogaCalculatedLayout); - } - ASDisplayNodeAssert(_yogaCalculatedLayout, @"Yoga node should have a non-nil layout at this stage: %@", self); - return _yogaCalculatedLayout; - } else { - // If we're a yoga leaf node with custom measurement function, proceed with normal layout so layoutSpecs can run (e.g. ASButtonNode). - ASYogaLog("PROCEEDING past Yoga check to calculate ASLayout for: %@", self); - } - } -#endif /* YOGA */ - - // Manual size calculation via calculateSizeThatFits: - if (_layoutSpecBlock == NULL && (_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits) == 0) { - CGSize size = [self calculateSizeThatFits:constrainedSize.max]; - ASDisplayNodeLogEvent(self, @"calculatedSize: %@", NSStringFromCGSize(size)); - return [ASLayout layoutWithLayoutElement:self size:ASSizeRangeClamp(constrainedSize, size) sublayouts:nil]; - } - - // Size calcualtion with layout elements - BOOL measureLayoutSpec = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutSpec; - if (measureLayoutSpec) { - _layoutSpecNumberOfPasses++; - } - - // Get layout element from the node - id layoutElement = [self _locked_layoutElementThatFits:constrainedSize]; -#if ASEnableVerboseLogging - for (NSString *asciiLine in [[layoutElement asciiArtString] componentsSeparatedByString:@"\n"]) { - as_log_verbose(ASLayoutLog(), "%@", asciiLine); - } -#endif - - - // Certain properties are necessary to set on an element of type ASLayoutSpec - if (layoutElement.layoutElementType == ASLayoutElementTypeLayoutSpec) { - ASLayoutSpec *layoutSpec = (ASLayoutSpec *)layoutElement; - -#if AS_DEDUPE_LAYOUT_SPEC_TREE - NSHashTable *duplicateElements = [layoutSpec findDuplicatedElementsInSubtree]; - if (duplicateElements.count > 0) { - ASDisplayNodeFailAssert(@"Node %@ returned a layout spec that contains the same elements in multiple positions. Elements: %@", self, duplicateElements); - // Use an empty layout spec to avoid crashes - layoutSpec = [[ASLayoutSpec alloc] init]; - } -#endif - - ASDisplayNodeAssert(layoutSpec.isMutable, @"Node %@ returned layout spec %@ that has already been used. Layout specs should always be regenerated.", self, layoutSpec); - - layoutSpec.isMutable = NO; - } - - // Manually propagate the trait collection here so that any layoutSpec children of layoutSpec will get a traitCollection - { - ASDN::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec); - ASTraitCollectionPropagateDown(layoutElement, self.primitiveTraitCollection); - } - - BOOL measureLayoutComputation = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutComputation; - if (measureLayoutComputation) { - _layoutComputationNumberOfPasses++; - } - - // Layout element layout creation - ASLayout *layout = ({ - ASDN::SumScopeTimer t(_layoutComputationTotalTime, measureLayoutComputation); - [layoutElement layoutThatFits:constrainedSize]; - }); - ASDisplayNodeAssertNotNil(layout, @"[ASLayoutElement layoutThatFits:] should never return nil! %@, %@", self, layout); - - // Make sure layoutElementObject of the root layout is `self`, so that the flattened layout will be structurally correct. - BOOL isFinalLayoutElement = (layout.layoutElement != self); - if (isFinalLayoutElement) { - layout.position = CGPointZero; - layout = [ASLayout layoutWithLayoutElement:self size:layout.size sublayouts:@[layout]]; - } - ASDisplayNodeLogEvent(self, @"computedLayout: %@", layout); - - // PR #1157: Reduces accuracy of _unflattenedLayout for debugging/Weaver - if ([ASDisplayNode shouldStoreUnflattenedLayouts]) { - _unflattenedLayout = layout; - } - layout = [layout filteredNodeLayoutTree]; - - return layout; -} - -- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize -{ - __ASDisplayNodeCheckForLayoutMethodOverrides; - - ASDisplayNodeLogEvent(self, @"calculateSizeThatFits: with constrainedSize: %@", NSStringFromCGSize(constrainedSize)); - - return ASIsCGSizeValidForSize(constrainedSize) ? constrainedSize : CGSizeZero; -} - -- (id)_locked_layoutElementThatFits:(ASSizeRange)constrainedSize -{ - ASAssertLocked(__instanceLock__); - __ASDisplayNodeCheckForLayoutMethodOverrides; - - BOOL measureLayoutSpec = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutSpec; - - if (_layoutSpecBlock != NULL) { - return ({ - ASDN::MutexLocker l(__instanceLock__); - ASDN::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec); - _layoutSpecBlock(self, constrainedSize); - }); - } else { - return ({ - ASDN::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec); - [self layoutSpecThatFits:constrainedSize]; - }); - } -} - -- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize -{ - __ASDisplayNodeCheckForLayoutMethodOverrides; - - ASDisplayNodeAssert(NO, @"-[ASDisplayNode layoutSpecThatFits:] should never return an empty value. One way this is caused is by calling -[super layoutSpecThatFits:] which is not currently supported."); - return [[ASLayoutSpec alloc] init]; -} - -- (void)layout -{ - // Hook for subclasses - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - ASDisplayNodeAssertTrue(self.isNodeLoaded); - [self enumerateInterfaceStateDelegates:^(id del) { - [del nodeDidLayout]; - }]; -} - -#pragma mark Layout Transition - -- (void)_layoutTransitionMeasurementDidFinish -{ - // Hook for subclasses - No-Op in ASDisplayNode -} - -#pragma mark <_ASTransitionContextCompletionDelegate> - -/** - * After completeTransition: is called on the ASContextTransitioning object in animateLayoutTransition: this - * delegate method will be called that start the completion process of the transition - */ -- (void)transitionContext:(_ASTransitionContext *)context didComplete:(BOOL)didComplete -{ - ASDisplayNodeAssertMainThread(); - - [self didCompleteLayoutTransition:context]; - - _pendingLayoutTransitionContext = nil; - - [self _pendingLayoutTransitionDidComplete]; -} - -- (void)calculatedLayoutDidChange -{ - // Subclass override -} - -#pragma mark - Display - -NSString * const ASRenderingEngineDidDisplayScheduledNodesNotification = @"ASRenderingEngineDidDisplayScheduledNodes"; -NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp"; - -- (BOOL)displaysAsynchronously -{ - ASDN::MutexLocker l(__instanceLock__); - return [self _locked_displaysAsynchronously]; -} - -/** - * Core implementation of -displaysAsynchronously. - */ -- (BOOL)_locked_displaysAsynchronously -{ - ASAssertLocked(__instanceLock__); - return checkFlag(Synchronous) == NO && _flags.displaysAsynchronously; -} - -- (void)setDisplaysAsynchronously:(BOOL)displaysAsynchronously -{ - ASDisplayNodeAssertThreadAffinity(self); - - ASDN::MutexLocker l(__instanceLock__); - - // Can't do this for synchronous nodes (using layers that are not _ASDisplayLayer and so we can't control display prevention/cancel) - if (checkFlag(Synchronous)) { - return; - } - - if (_flags.displaysAsynchronously == displaysAsynchronously) { - return; - } - - _flags.displaysAsynchronously = displaysAsynchronously; - - self._locked_asyncLayer.displaysAsynchronously = displaysAsynchronously; -} - -- (BOOL)rasterizesSubtree -{ - ASDN::MutexLocker l(__instanceLock__); - return _flags.rasterizesSubtree; -} - -- (void)enableSubtreeRasterization -{ - ASDN::MutexLocker l(__instanceLock__); - // Already rasterized from self. - if (_flags.rasterizesSubtree) { - return; - } - - // If rasterized from above, bail. - if (ASHierarchyStateIncludesRasterized(_hierarchyState)) { - ASDisplayNodeFailAssert(@"Subnode of a rasterized node should not have redundant -enableSubtreeRasterization."); - return; - } - - // Ensure not loaded. - if ([self _locked_isNodeLoaded]) { - ASDisplayNodeFailAssert(@"Cannot call %@ on loaded node: %@", NSStringFromSelector(_cmd), self); - return; - } - - // Ensure no loaded subnodes - ASDisplayNode *loadedSubnode = ASDisplayNodeFindFirstSubnode(self, ^BOOL(ASDisplayNode * _Nonnull node) { - return node.nodeLoaded; - }); - if (loadedSubnode != nil) { - ASDisplayNodeFailAssert(@"Cannot call %@ on node %@ with loaded subnode %@", NSStringFromSelector(_cmd), self, loadedSubnode); - return; - } - - _flags.rasterizesSubtree = YES; - - // Tell subnodes that now they're in a rasterized hierarchy (while holding lock!) - for (ASDisplayNode *subnode in _subnodes) { - [subnode enterHierarchyState:ASHierarchyStateRasterized]; - } -} - -- (CGFloat)contentsScaleForDisplay -{ - ASDN::MutexLocker l(__instanceLock__); - - return _contentsScaleForDisplay; -} - -- (void)setContentsScaleForDisplay:(CGFloat)contentsScaleForDisplay -{ - ASDN::MutexLocker l(__instanceLock__); - - if (_contentsScaleForDisplay == contentsScaleForDisplay) { - return; - } - - _contentsScaleForDisplay = contentsScaleForDisplay; -} - -- (void)displayImmediately -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(!checkFlag(Synchronous), @"this method is designed for asynchronous mode only"); - - [self.asyncLayer displayImmediately]; -} - -- (void)recursivelyDisplayImmediately -{ - for (ASDisplayNode *child in self.subnodes) { - [child recursivelyDisplayImmediately]; - } - [self displayImmediately]; -} - -- (void)__setNeedsDisplay -{ - BOOL shouldScheduleForDisplay = NO; - { - ASDN::MutexLocker l(__instanceLock__); - BOOL nowDisplay = ASInterfaceStateIncludesDisplay(_interfaceState); - // FIXME: This should not need to recursively display, so create a non-recursive variant. - // The semantics of setNeedsDisplay (as defined by CALayer behavior) are not recursive. - if (_layer != nil && !checkFlag(Synchronous) && nowDisplay && [self _implementsDisplay]) { - shouldScheduleForDisplay = YES; - } - } - - if (shouldScheduleForDisplay) { - [ASDisplayNode scheduleNodeForRecursiveDisplay:self]; - } -} - -+ (void)scheduleNodeForRecursiveDisplay:(ASDisplayNode *)node -{ - static dispatch_once_t onceToken; - static ASRunLoopQueue *renderQueue; - dispatch_once(&onceToken, ^{ - renderQueue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() - retainObjects:NO - handler:^(ASDisplayNode * _Nonnull dequeuedItem, BOOL isQueueDrained) { - [dequeuedItem _recursivelyTriggerDisplayAndBlock:NO]; - if (isQueueDrained) { - CFTimeInterval timestamp = CACurrentMediaTime(); - [[NSNotificationCenter defaultCenter] postNotificationName:ASRenderingEngineDidDisplayScheduledNodesNotification - object:nil - userInfo:@{ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp: @(timestamp)}]; - } - }]; - }); - - as_log_verbose(ASDisplayLog(), "%s %@", sel_getName(_cmd), node); - [renderQueue enqueue:node]; -} - -/// Helper method to summarize whether or not the node run through the display process -- (BOOL)_implementsDisplay -{ - ASDN::MutexLocker l(__instanceLock__); - - return _flags.implementsDrawRect || _flags.implementsImageDisplay || _flags.rasterizesSubtree; -} - -// Track that a node will be displayed as part of the current node hierarchy. -// The node sending the message should usually be passed as the parameter, similar to the delegation pattern. -- (void)_pendingNodeWillDisplay:(ASDisplayNode *)node -{ - ASDisplayNodeAssertMainThread(); - - // No lock needed as _pendingDisplayNodes is main thread only - if (!_pendingDisplayNodes) { - _pendingDisplayNodes = [[ASWeakSet alloc] init]; - } - - [_pendingDisplayNodes addObject:node]; -} - -// Notify that a node that was pending display finished -// The node sending the message should usually be passed as the parameter, similar to the delegation pattern. -- (void)_pendingNodeDidDisplay:(ASDisplayNode *)node -{ - ASDisplayNodeAssertMainThread(); - - // No lock for _pendingDisplayNodes needed as it's main thread only - [_pendingDisplayNodes removeObject:node]; - - if (_pendingDisplayNodes.isEmpty) { - - [self hierarchyDisplayDidFinish]; - [self enumerateInterfaceStateDelegates:^(id delegate) { - [delegate hierarchyDisplayDidFinish]; - }]; - - BOOL placeholderShouldPersist = [self placeholderShouldPersist]; - - __instanceLock__.lock(); - if (_placeholderLayer.superlayer && !placeholderShouldPersist) { - void (^cleanupBlock)() = ^{ - [_placeholderLayer removeFromSuperlayer]; - }; - - if (_placeholderFadeDuration > 0.0 && ASInterfaceStateIncludesVisible(self.interfaceState)) { - [CATransaction begin]; - [CATransaction setCompletionBlock:cleanupBlock]; - [CATransaction setAnimationDuration:_placeholderFadeDuration]; - _placeholderLayer.opacity = 0.0; - [CATransaction commit]; - } else { - cleanupBlock(); - } - } - __instanceLock__.unlock(); - } -} - -- (void)hierarchyDisplayDidFinish -{ - // Subclass hook -} - -// Helper method to determine if it's safe to call setNeedsDisplay on a layer without throwing away the content. -// For details look at the comment on the canCallSetNeedsDisplayOfLayer flag -- (BOOL)_canCallSetNeedsDisplayOfLayer -{ - ASDN::MutexLocker l(__instanceLock__); - return _flags.canCallSetNeedsDisplayOfLayer; -} - -void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) -{ - // This recursion must handle layers in various states: - // 1. Just added to hierarchy, CA hasn't yet called -display - // 2. Previously in a hierarchy (such as a working window owned by an Intelligent Preloading class, like ASTableView / ASCollectionView / ASViewController) - // 3. Has no content to display at all - // Specifically for case 1), we need to explicitly trigger a -display call now. - // Otherwise, there is no opportunity to block the main thread after CoreAnimation's transaction commit - // (even a runloop observer at a late call order will not stop the next frame from compositing, showing placeholders). - - ASDisplayNode *node = [layer asyncdisplaykit_node]; - - if (node.isSynchronous && [node _canCallSetNeedsDisplayOfLayer]) { - // Layers for UIKit components that are wrapped within a node needs to be set to be displayed as the contents of - // the layer get's cleared and would not be recreated otherwise. - // We do not call this for _ASDisplayLayer as an optimization. - [layer setNeedsDisplay]; - } - - if ([node _implementsDisplay]) { - // For layers that do get displayed here, this immediately kicks off the work on the concurrent -[_ASDisplayLayer displayQueue]. - // At the same time, it creates an associated _ASAsyncTransaction, which we can use to block on display completion. See ASDisplayNode+AsyncDisplay.mm. - [layer displayIfNeeded]; - } - - // Kick off the recursion first, so that all necessary display calls are sent and the displayQueue is full of parallelizable work. - // NOTE: The docs report that `sublayers` returns a copy but it actually doesn't. - for (CALayer *sublayer in [layer.sublayers copy]) { - recursivelyTriggerDisplayForLayer(sublayer, shouldBlock); - } - - if (shouldBlock) { - // As the recursion unwinds, verify each transaction is complete and block if it is not. - // While blocking on one transaction, others may be completing concurrently, so it doesn't matter which blocks first. - BOOL waitUntilComplete = (!node.shouldBypassEnsureDisplay); - if (waitUntilComplete) { - for (_ASAsyncTransaction *transaction in [layer.asyncdisplaykit_asyncLayerTransactions copy]) { - // Even if none of the layers have had a chance to start display earlier, they will still be allowed to saturate a multicore CPU while blocking main. - // This significantly reduces time on the main thread relative to UIKit. - [transaction waitUntilComplete]; - } - } - } -} - -- (void)_recursivelyTriggerDisplayAndBlock:(BOOL)shouldBlock -{ - ASDisplayNodeAssertMainThread(); - - CALayer *layer = self.layer; - // -layoutIfNeeded is recursive, and even walks up to superlayers to check if they need layout, - // so we should call it outside of starting the recursion below. If our own layer is not marked - // as dirty, we can assume layout has run on this subtree before. - if ([layer needsLayout]) { - [layer layoutIfNeeded]; - } - recursivelyTriggerDisplayForLayer(layer, shouldBlock); -} - -- (void)recursivelyEnsureDisplaySynchronously:(BOOL)synchronously -{ - [self _recursivelyTriggerDisplayAndBlock:synchronously]; -} - -- (void)setShouldBypassEnsureDisplay:(BOOL)shouldBypassEnsureDisplay -{ - ASDN::MutexLocker l(__instanceLock__); - _flags.shouldBypassEnsureDisplay = shouldBypassEnsureDisplay; -} - -- (BOOL)shouldBypassEnsureDisplay -{ - ASDN::MutexLocker l(__instanceLock__); - return _flags.shouldBypassEnsureDisplay; -} - -- (void)setNeedsDisplayAtScale:(CGFloat)contentsScale -{ - { - ASDN::MutexLocker l(__instanceLock__); - if (contentsScale == _contentsScaleForDisplay) { - return; - } - - _contentsScaleForDisplay = contentsScale; - } - - [self setNeedsDisplay]; -} - -- (void)recursivelySetNeedsDisplayAtScale:(CGFloat)contentsScale -{ - ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode *node) { - [node setNeedsDisplayAtScale:contentsScale]; - }); -} - -- (void)_layoutClipCornersIfNeeded -{ - ASDisplayNodeAssertMainThread(); - if (_clipCornerLayers[0] == nil) { - return; - } - - CGSize boundsSize = self.bounds.size; - for (int idx = 0; idx < NUM_CLIP_CORNER_LAYERS; idx++) { - BOOL isTop = (idx == 0 || idx == 1); - BOOL isRight = (idx == 1 || idx == 2); - if (_clipCornerLayers[idx]) { - // Note the Core Animation coordinates are reversed for y; 0 is at the bottom. - _clipCornerLayers[idx].position = CGPointMake(isRight ? boundsSize.width : 0.0, isTop ? boundsSize.height : 0.0); - [_layer addSublayer:_clipCornerLayers[idx]]; - } - } -} - -- (void)_updateClipCornerLayerContentsWithRadius:(CGFloat)radius backgroundColor:(UIColor *)backgroundColor -{ - ASPerformBlockOnMainThread(^{ - for (int idx = 0; idx < NUM_CLIP_CORNER_LAYERS; idx++) { - // Layers are, in order: Top Left, Top Right, Bottom Right, Bottom Left. - // anchorPoint is Bottom Left at 0,0 and Top Right at 1,1. - BOOL isTop = (idx == 0 || idx == 1); - BOOL isRight = (idx == 1 || idx == 2); - - CGSize size = CGSizeMake(radius + 1, radius + 1); - ASGraphicsBeginImageContextWithOptions(size, NO, self.contentsScaleForDisplay); - - CGContextRef ctx = UIGraphicsGetCurrentContext(); - if (isRight == YES) { - CGContextTranslateCTM(ctx, -radius + 1, 0); - } - if (isTop == YES) { - CGContextTranslateCTM(ctx, 0, -radius + 1); - } - UIBezierPath *roundedRect = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, radius * 2, radius * 2) cornerRadius:radius]; - [roundedRect setUsesEvenOddFillRule:YES]; - [roundedRect appendPath:[UIBezierPath bezierPathWithRect:CGRectMake(-1, -1, radius * 2 + 1, radius * 2 + 1)]]; - [backgroundColor setFill]; - [roundedRect fill]; - - // No lock needed, as _clipCornerLayers is only modified on the main thread. - CALayer *clipCornerLayer = _clipCornerLayers[idx]; - clipCornerLayer.contents = (id)(ASGraphicsGetImageAndEndCurrentContext().CGImage); - clipCornerLayer.bounds = CGRectMake(0.0, 0.0, size.width, size.height); - clipCornerLayer.anchorPoint = CGPointMake(isRight ? 1.0 : 0.0, isTop ? 1.0 : 0.0); - } - [self _layoutClipCornersIfNeeded]; - }); -} - -- (void)_setClipCornerLayersVisible:(BOOL)visible -{ - ASPerformBlockOnMainThread(^{ - ASDisplayNodeAssertMainThread(); - if (visible) { - for (int idx = 0; idx < NUM_CLIP_CORNER_LAYERS; idx++) { - if (_clipCornerLayers[idx] == nil) { - static ASDisplayNodeCornerLayerDelegate *clipCornerLayers; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - clipCornerLayers = [[ASDisplayNodeCornerLayerDelegate alloc] init]; - }); - _clipCornerLayers[idx] = [[CALayer alloc] init]; - _clipCornerLayers[idx].zPosition = 99999; - _clipCornerLayers[idx].delegate = clipCornerLayers; - } - } - [self _updateClipCornerLayerContentsWithRadius:_cornerRadius backgroundColor:self.backgroundColor]; - } else { - for (int idx = 0; idx < NUM_CLIP_CORNER_LAYERS; idx++) { - [_clipCornerLayers[idx] removeFromSuperlayer]; - _clipCornerLayers[idx] = nil; - } - } - }); -} - -- (void)updateCornerRoundingWithType:(ASCornerRoundingType)newRoundingType cornerRadius:(CGFloat)newCornerRadius -{ - __instanceLock__.lock(); - CGFloat oldCornerRadius = _cornerRadius; - ASCornerRoundingType oldRoundingType = _cornerRoundingType; - - _cornerRadius = newCornerRadius; - _cornerRoundingType = newRoundingType; - __instanceLock__.unlock(); - - ASPerformBlockOnMainThread(^{ - ASDisplayNodeAssertMainThread(); - - if (oldRoundingType != newRoundingType || oldCornerRadius != newCornerRadius) { - if (oldRoundingType == ASCornerRoundingTypeDefaultSlowCALayer) { - if (newRoundingType == ASCornerRoundingTypePrecomposited) { - self.layerCornerRadius = 0.0; - if (oldCornerRadius > 0.0) { - [self displayImmediately]; - } else { - [self setNeedsDisplay]; // Async display is OK if we aren't replacing an existing .cornerRadius. - } - } - else if (newRoundingType == ASCornerRoundingTypeClipping) { - self.layerCornerRadius = 0.0; - [self _setClipCornerLayersVisible:YES]; - } else if (newRoundingType == ASCornerRoundingTypeDefaultSlowCALayer) { - self.layerCornerRadius = newCornerRadius; - } - } - else if (oldRoundingType == ASCornerRoundingTypePrecomposited) { - if (newRoundingType == ASCornerRoundingTypeDefaultSlowCALayer) { - self.layerCornerRadius = newCornerRadius; - [self setNeedsDisplay]; - } - else if (newRoundingType == ASCornerRoundingTypePrecomposited) { - // Corners are already precomposited, but the radius has changed. - // Default to async re-display. The user may force a synchronous display if desired. - [self setNeedsDisplay]; - } - else if (newRoundingType == ASCornerRoundingTypeClipping) { - [self _setClipCornerLayersVisible:YES]; - [self setNeedsDisplay]; - } - } - else if (oldRoundingType == ASCornerRoundingTypeClipping) { - if (newRoundingType == ASCornerRoundingTypeDefaultSlowCALayer) { - self.layerCornerRadius = newCornerRadius; - [self _setClipCornerLayersVisible:NO]; - } - else if (newRoundingType == ASCornerRoundingTypePrecomposited) { - [self _setClipCornerLayersVisible:NO]; - [self displayImmediately]; - } - else if (newRoundingType == ASCornerRoundingTypeClipping) { - // Clip corners already exist, but the radius has changed. - [self _updateClipCornerLayerContentsWithRadius:newCornerRadius backgroundColor:self.backgroundColor]; - } - } - } - }); -} - -- (void)recursivelySetDisplaySuspended:(BOOL)flag -{ - _recursivelySetDisplaySuspended(self, nil, flag); -} - -// TODO: Replace this with ASDisplayNodePerformBlockOnEveryNode or a variant with a condition / test block. -static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer, BOOL flag) -{ - // If there is no layer, but node whose its view is loaded, then we can traverse down its layer hierarchy. Otherwise we must stick to the node hierarchy to avoid loading views prematurely. Note that for nodes that haven't loaded their views, they can't possibly have subviews/sublayers, so we don't need to traverse the layer hierarchy for them. - if (!layer && node && node.nodeLoaded) { - layer = node.layer; - } - - // If we don't know the node, but the layer is an async layer, get the node from the layer. - if (!node && layer && [layer isKindOfClass:[_ASDisplayLayer class]]) { - node = layer.asyncdisplaykit_node; - } - - // Set the flag on the node. If this is a pure layer (no node) then this has no effect (plain layers don't support preventing/cancelling display). - node.displaySuspended = flag; - - if (layer && !node.rasterizesSubtree) { - // If there is a layer, recurse down the layer hierarchy to set the flag on descendants. This will cover both layer-based and node-based children. - for (CALayer *sublayer in layer.sublayers) { - _recursivelySetDisplaySuspended(nil, sublayer, flag); - } - } else { - // If there is no layer (view not loaded yet) or this node rasterizes descendants (there won't be a layer tree to traverse), recurse down the subnode hierarchy to set the flag on descendants. This covers only node-based children, but for a node whose view is not loaded it can't possibly have nodeless children. - for (ASDisplayNode *subnode in node.subnodes) { - _recursivelySetDisplaySuspended(subnode, nil, flag); - } - } -} - -- (BOOL)displaySuspended -{ - ASDN::MutexLocker l(__instanceLock__); - return _flags.displaySuspended; -} - -- (void)setDisplaySuspended:(BOOL)flag -{ - ASDisplayNodeAssertThreadAffinity(self); - __instanceLock__.lock(); - - // Can't do this for synchronous nodes (using layers that are not _ASDisplayLayer and so we can't control display prevention/cancel) - if (checkFlag(Synchronous) || _flags.displaySuspended == flag) { - __instanceLock__.unlock(); - return; - } - - _flags.displaySuspended = flag; - - self._locked_asyncLayer.displaySuspended = flag; - - ASDisplayNode *supernode = _supernode; - __instanceLock__.unlock(); - - if ([self _implementsDisplay]) { - // Display start and finish methods needs to happen on the main thread - ASPerformBlockOnMainThread(^{ - if (flag) { - [supernode subnodeDisplayDidFinish:self]; - } else { - [supernode subnodeDisplayWillStart:self]; - } - }); - } -} - -#pragma mark <_ASDisplayLayerDelegate> - -- (void)willDisplayAsyncLayer:(_ASDisplayLayer *)layer asynchronously:(BOOL)asynchronously -{ - // Subclass hook. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [self displayWillStart]; -#pragma clang diagnostic pop - - [self displayWillStartAsynchronously:asynchronously]; -} - -- (void)didDisplayAsyncLayer:(_ASDisplayLayer *)layer -{ - // Subclass hook. - [self displayDidFinish]; -} - -- (void)displayWillStart {} -- (void)displayWillStartAsynchronously:(BOOL)asynchronously -{ - ASDisplayNodeAssertMainThread(); - - ASDisplayNodeLogEvent(self, @"displayWillStart"); - // in case current node takes longer to display than it's subnodes, treat it as a dependent node - [self _pendingNodeWillDisplay:self]; - - __instanceLock__.lock(); - ASDisplayNode *supernode = _supernode; - __instanceLock__.unlock(); - - [supernode subnodeDisplayWillStart:self]; -} - -- (void)displayDidFinish -{ - ASDisplayNodeAssertMainThread(); - - ASDisplayNodeLogEvent(self, @"displayDidFinish"); - [self _pendingNodeDidDisplay:self]; - - __instanceLock__.lock(); - ASDisplayNode *supernode = _supernode; - __instanceLock__.unlock(); - - [supernode subnodeDisplayDidFinish:self]; -} - -- (void)subnodeDisplayWillStart:(ASDisplayNode *)subnode -{ - // Subclass hook - [self _pendingNodeWillDisplay:subnode]; -} - -- (void)subnodeDisplayDidFinish:(ASDisplayNode *)subnode -{ - // Subclass hook - [self _pendingNodeDidDisplay:subnode]; -} - -#pragma mark - -// We are only the delegate for the layer when we are layer-backed, as UIView performs this function normally -- (id)actionForLayer:(CALayer *)layer forKey:(NSString *)event -{ - if (event == kCAOnOrderIn) { - [self __enterHierarchy]; - } else if (event == kCAOnOrderOut) { - [self __exitHierarchy]; - } - - ASDisplayNodeAssert(_flags.layerBacked, @"We shouldn't get called back here unless we are layer-backed."); - return (id)kCFNull; -} - -#pragma mark - Error Handling - -+ (void)setNonFatalErrorBlock:(ASDisplayNodeNonFatalErrorBlock)nonFatalErrorBlock -{ - if (_nonFatalErrorBlock != nonFatalErrorBlock) { - _nonFatalErrorBlock = [nonFatalErrorBlock copy]; - } -} - -+ (ASDisplayNodeNonFatalErrorBlock)nonFatalErrorBlock -{ - return _nonFatalErrorBlock; -} - -#pragma mark - Converting to and from the Node's Coordinate System - -- (CATransform3D)_transformToAncestor:(ASDisplayNode *)ancestor -{ - CATransform3D transform = CATransform3DIdentity; - ASDisplayNode *currentNode = self; - while (currentNode.supernode) { - if (currentNode == ancestor) { - return transform; - } - - CGPoint anchorPoint = currentNode.anchorPoint; - CGRect bounds = currentNode.bounds; - CGPoint position = currentNode.position; - CGPoint origin = CGPointMake(position.x - bounds.size.width * anchorPoint.x, - position.y - bounds.size.height * anchorPoint.y); - - transform = CATransform3DTranslate(transform, origin.x, origin.y, 0); - transform = CATransform3DTranslate(transform, -bounds.origin.x, -bounds.origin.y, 0); - currentNode = currentNode.supernode; - } - return transform; -} - -static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNode *referenceNode, ASDisplayNode *targetNode) -{ - ASDisplayNode *ancestor = ASDisplayNodeFindClosestCommonAncestor(referenceNode, targetNode); - - // Transform into global (away from reference coordinate space) - CATransform3D transformToGlobal = [referenceNode _transformToAncestor:ancestor]; - - // Transform into local (via inverse transform from target to ancestor) - CATransform3D transformToLocal = CATransform3DInvert([targetNode _transformToAncestor:ancestor]); - - return CATransform3DConcat(transformToGlobal, transformToLocal); -} - -- (CGPoint)convertPoint:(CGPoint)point fromNode:(ASDisplayNode *)node -{ - ASDisplayNodeAssertThreadAffinity(self); - - /** - * When passed node=nil, all methods in this family use the UIView-style - * behavior – that is, convert from/to window coordinates if there's a window, - * otherwise return the point untransformed. - */ - if (node == nil && self.nodeLoaded) { - CALayer *layer = self.layer; - if (UIWindow *window = ASFindWindowOfLayer(layer)) { - return [layer convertPoint:point fromLayer:window.layer]; - } else { - return point; - } - } - - // Get root node of the accessible node hierarchy, if node not specified - node = node ? : ASDisplayNodeUltimateParentOfNode(self); - - // Calculate transform to map points between coordinate spaces - CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(node, self); - CGAffineTransform flattenedTransform = CATransform3DGetAffineTransform(nodeTransform); - ASDisplayNodeAssertTrue(CATransform3DIsAffine(nodeTransform)); - - // Apply to point - return CGPointApplyAffineTransform(point, flattenedTransform); -} - -- (CGPoint)convertPoint:(CGPoint)point toNode:(ASDisplayNode *)node -{ - ASDisplayNodeAssertThreadAffinity(self); - - if (node == nil && self.nodeLoaded) { - CALayer *layer = self.layer; - if (UIWindow *window = ASFindWindowOfLayer(layer)) { - return [layer convertPoint:point toLayer:window.layer]; - } else { - return point; - } - } - - // Get root node of the accessible node hierarchy, if node not specified - node = node ? : ASDisplayNodeUltimateParentOfNode(self); - - // Calculate transform to map points between coordinate spaces - CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(self, node); - CGAffineTransform flattenedTransform = CATransform3DGetAffineTransform(nodeTransform); - ASDisplayNodeAssertTrue(CATransform3DIsAffine(nodeTransform)); - - // Apply to point - return CGPointApplyAffineTransform(point, flattenedTransform); -} - -- (CGRect)convertRect:(CGRect)rect fromNode:(ASDisplayNode *)node -{ - ASDisplayNodeAssertThreadAffinity(self); - - if (node == nil && self.nodeLoaded) { - CALayer *layer = self.layer; - if (UIWindow *window = ASFindWindowOfLayer(layer)) { - return [layer convertRect:rect fromLayer:window.layer]; - } else { - return rect; - } - } - - // Get root node of the accessible node hierarchy, if node not specified - node = node ? : ASDisplayNodeUltimateParentOfNode(self); - - // Calculate transform to map points between coordinate spaces - CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(node, self); - CGAffineTransform flattenedTransform = CATransform3DGetAffineTransform(nodeTransform); - ASDisplayNodeAssertTrue(CATransform3DIsAffine(nodeTransform)); - - // Apply to rect - return CGRectApplyAffineTransform(rect, flattenedTransform); -} - -- (CGRect)convertRect:(CGRect)rect toNode:(ASDisplayNode *)node -{ - ASDisplayNodeAssertThreadAffinity(self); - - if (node == nil && self.nodeLoaded) { - CALayer *layer = self.layer; - if (UIWindow *window = ASFindWindowOfLayer(layer)) { - return [layer convertRect:rect toLayer:window.layer]; - } else { - return rect; - } - } - - // Get root node of the accessible node hierarchy, if node not specified - node = node ? : ASDisplayNodeUltimateParentOfNode(self); - - // Calculate transform to map points between coordinate spaces - CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(self, node); - CGAffineTransform flattenedTransform = CATransform3DGetAffineTransform(nodeTransform); - ASDisplayNodeAssertTrue(CATransform3DIsAffine(nodeTransform)); - - // Apply to rect - return CGRectApplyAffineTransform(rect, flattenedTransform); -} - -#pragma mark - Managing the Node Hierarchy - -ASDISPLAYNODE_INLINE bool shouldDisableNotificationsForMovingBetweenParents(ASDisplayNode *from, ASDisplayNode *to) { - if (!from || !to) return NO; - if (from.isSynchronous) return NO; - if (to.isSynchronous) return NO; - if (from.isInHierarchy != to.isInHierarchy) return NO; - return YES; -} - -/// Returns incremented value of i if i is not NSNotFound -ASDISPLAYNODE_INLINE NSInteger incrementIfFound(NSInteger i) { - return i == NSNotFound ? NSNotFound : i + 1; -} - -/// Returns if a node is a member of a rasterized tree -ASDISPLAYNODE_INLINE BOOL canUseViewAPI(ASDisplayNode *node, ASDisplayNode *subnode) { - return (subnode.isLayerBacked == NO && node.isLayerBacked == NO); -} - -/// Returns if node is a member of a rasterized tree -ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { - return (node.rasterizesSubtree || (node.hierarchyState & ASHierarchyStateRasterized)); -} - -// NOTE: This method must be dealloc-safe (should not retain self). -- (ASDisplayNode *)supernode -{ -#if CHECK_LOCKING_SAFETY - if (__instanceLock__.locked()) { - NSLog(@"WARNING: Accessing supernode while holding recursive instance lock of this node is worrisome. It's likely that you will soon try to acquire the supernode's lock, and this can easily cause deadlocks."); - } -#endif - - ASDN::MutexLocker l(__instanceLock__); - return _supernode; -} - -- (void)_setSupernode:(ASDisplayNode *)newSupernode -{ - BOOL supernodeDidChange = NO; - ASDisplayNode *oldSupernode = nil; - { - ASDN::MutexLocker l(__instanceLock__); - if (_supernode != newSupernode) { - oldSupernode = _supernode; // Access supernode properties outside of lock to avoid remote chance of deadlock, - // in case supernode implementation must access one of our properties. - _supernode = newSupernode; - supernodeDidChange = YES; - } - } - - if (supernodeDidChange) { - ASDisplayNodeLogEvent(self, @"supernodeDidChange: %@, oldValue = %@", ASObjectDescriptionMakeTiny(newSupernode), ASObjectDescriptionMakeTiny(oldSupernode)); - // Hierarchy state - ASHierarchyState stateToEnterOrExit = (newSupernode ? newSupernode.hierarchyState - : oldSupernode.hierarchyState); - - // Rasterized state - BOOL parentWasOrIsRasterized = (newSupernode ? newSupernode.rasterizesSubtree - : oldSupernode.rasterizesSubtree); - if (parentWasOrIsRasterized) { - stateToEnterOrExit |= ASHierarchyStateRasterized; - } - if (newSupernode) { - [self enterHierarchyState:stateToEnterOrExit]; - - // If a node was added to a supernode, the supernode could be in a layout pending state. All of the hierarchy state - // properties related to the transition need to be copied over as well as propagated down the subtree. - // This is especially important as with automatic subnode management, adding subnodes can happen while a transition - // is in fly - if (ASHierarchyStateIncludesLayoutPending(stateToEnterOrExit)) { - int32_t pendingTransitionId = newSupernode->_pendingTransitionID; - if (pendingTransitionId != ASLayoutElementContextInvalidTransitionID) { - { - _pendingTransitionID = pendingTransitionId; - - // Propagate down the new pending transition id - ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) { - node->_pendingTransitionID = pendingTransitionId; - }); - } - } - } - - // Now that we have a supernode, propagate its traits to self. - ASTraitCollectionPropagateDown(self, newSupernode.primitiveTraitCollection); - - } else { - // If a node will be removed from the supernode it should go out from the layout pending state to remove all - // layout pending state related properties on the node - stateToEnterOrExit |= ASHierarchyStateLayoutPending; - - [self exitHierarchyState:stateToEnterOrExit]; - - // We only need to explicitly exit hierarchy here if we were rasterized. - // Otherwise we will exit the hierarchy when our view/layer does so - // which has some nice carry-over machinery to handle cases where we are removed from a hierarchy - // and then added into it again shortly after. - __instanceLock__.lock(); - BOOL isInHierarchy = _flags.isInHierarchy; - __instanceLock__.unlock(); - - if (parentWasOrIsRasterized && isInHierarchy) { - [self __exitHierarchy]; - } - } - } -} - -- (NSArray *)subnodes -{ - ASDN::MutexLocker l(__instanceLock__); - if (_cachedSubnodes == nil) { - _cachedSubnodes = [_subnodes copy]; - } else { - ASDisplayNodeAssert(ASObjectIsEqual(_cachedSubnodes, _subnodes), @"Expected _subnodes and _cachedSubnodes to have the same contents."); - } - return _cachedSubnodes ?: @[]; -} - -/* - * Central private helper method that should eventually be called if submethods add, insert or replace subnodes - * This method is called with thread affinity and without lock held. - * - * @param subnode The subnode to insert - * @param subnodeIndex The index in _subnodes to insert it - * @param viewSublayerIndex The index in layer.sublayers (not view.subviews) at which to insert the view (use if we can use the view API) otherwise pass NSNotFound - * @param sublayerIndex The index in layer.sublayers at which to insert the layer (use if either parent or subnode is layer-backed) otherwise pass NSNotFound - * @param oldSubnode Remove this subnode before inserting; ok to be nil if no removal is desired - */ -- (void)_insertSubnode:(ASDisplayNode *)subnode atSubnodeIndex:(NSInteger)subnodeIndex sublayerIndex:(NSInteger)sublayerIndex andRemoveSubnode:(ASDisplayNode *)oldSubnode -{ - ASDisplayNodeAssertThreadAffinity(self); - ASAssertUnlocked(__instanceLock__); - - as_log_verbose(ASNodeLog(), "Insert subnode %@ at index %zd of %@ and remove subnode %@", subnode, subnodeIndex, self, oldSubnode); - - if (subnode == nil || subnode == self) { - ASDisplayNodeFailAssert(@"Cannot insert a nil subnode or self as subnode"); - return; - } - - if (subnodeIndex == NSNotFound) { - ASDisplayNodeFailAssert(@"Try to insert node on an index that was not found"); - return; - } - - if (self.layerBacked && !subnode.layerBacked) { - ASDisplayNodeFailAssert(@"Cannot add a view-backed node as a subnode of a layer-backed node. Supernode: %@, subnode: %@", self, subnode); - return; - } - - BOOL isRasterized = subtreeIsRasterized(self); - if (isRasterized && subnode.nodeLoaded) { - ASDisplayNodeFailAssert(@"Cannot add loaded node %@ to rasterized subtree of node %@", ASObjectDescriptionMakeTiny(subnode), ASObjectDescriptionMakeTiny(self)); - return; - } - - __instanceLock__.lock(); - NSUInteger subnodesCount = _subnodes.count; - __instanceLock__.unlock(); - if (subnodeIndex > subnodesCount || subnodeIndex < 0) { - ASDisplayNodeFailAssert(@"Cannot insert a subnode at index %ld. Count is %ld", (long)subnodeIndex, (long)subnodesCount); - return; - } - - // Disable appearance methods during move between supernodes, but make sure we restore their state after we do our thing - ASDisplayNode *oldParent = subnode.supernode; - BOOL disableNotifications = shouldDisableNotificationsForMovingBetweenParents(oldParent, self); - if (disableNotifications) { - [subnode __incrementVisibilityNotificationsDisabled]; - } - - [subnode _removeFromSupernode]; - [oldSubnode _removeFromSupernode]; - - __instanceLock__.lock(); - if (_subnodes == nil) { - _subnodes = [[NSMutableArray alloc] init]; - } - [_subnodes insertObject:subnode atIndex:subnodeIndex]; - _cachedSubnodes = nil; - __instanceLock__.unlock(); - - // This call will apply our .hierarchyState to the new subnode. - // If we are a managed hierarchy, as in ASCellNode trees, it will also apply our .interfaceState. - [subnode _setSupernode:self]; - - // If this subnode will be rasterized, enter hierarchy if needed - // TODO: Move this into _setSupernode: ? - if (isRasterized) { - if (self.inHierarchy) { - [subnode __enterHierarchy]; - } - } else if (self.nodeLoaded) { - // If not rasterizing, and node is loaded insert the subview/sublayer now. - [self _insertSubnodeSubviewOrSublayer:subnode atIndex:sublayerIndex]; - } // Otherwise we will insert subview/sublayer when we get loaded - - ASDisplayNodeAssert(disableNotifications == shouldDisableNotificationsForMovingBetweenParents(oldParent, self), @"Invariant violated"); - if (disableNotifications) { - [subnode __decrementVisibilityNotificationsDisabled]; - } -} - -/* - * Inserts the view or layer of the given node at the given index - * - * @param subnode The subnode to insert - * @param idx The index in _view.subviews or _layer.sublayers at which to insert the subnode.view or - * subnode.layer of the subnode - */ -- (void)_insertSubnodeSubviewOrSublayer:(ASDisplayNode *)subnode atIndex:(NSInteger)idx -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(self.nodeLoaded, @"_insertSubnodeSubviewOrSublayer:atIndex: should never be called before our own view is created"); - - ASDisplayNodeAssert(idx != NSNotFound, @"Try to insert node on an index that was not found"); - if (idx == NSNotFound) { - return; - } - - // Because the view and layer can only be created and destroyed on Main, that is also the only thread - // where the view and layer can change. We can avoid locking. - - // If we can use view API, do. Due to an apple bug, -insertSubview:atIndex: actually wants a LAYER index, - // which we pass in. - if (canUseViewAPI(self, subnode)) { - [_view insertSubview:subnode.view atIndex:idx]; - } else { - [_layer insertSublayer:subnode.layer atIndex:(unsigned int)idx]; - } -} - -- (void)addSubnode:(ASDisplayNode *)subnode -{ - ASDisplayNodeLogEvent(self, @"addSubnode: %@ with automaticallyManagesSubnodes: %@", - subnode, self.automaticallyManagesSubnodes ? @"YES" : @"NO"); - [self _addSubnode:subnode]; -} - -- (void)_addSubnode:(ASDisplayNode *)subnode -{ - ASDisplayNodeAssertThreadAffinity(self); - - ASDisplayNodeAssert(subnode, @"Cannot insert a nil subnode"); - - // Don't add if it's already a subnode - ASDisplayNode *oldParent = subnode.supernode; - if (!subnode || subnode == self || oldParent == self) { - return; - } - - NSUInteger subnodesIndex; - NSUInteger sublayersIndex; - { - ASDN::MutexLocker l(__instanceLock__); - subnodesIndex = _subnodes.count; - sublayersIndex = _layer.sublayers.count; - } - - [self _insertSubnode:subnode atSubnodeIndex:subnodesIndex sublayerIndex:sublayersIndex andRemoveSubnode:nil]; -} - -- (void)_addSubnodeViewsAndLayers -{ - ASDisplayNodeAssertMainThread(); - - TIME_SCOPED(_debugTimeToAddSubnodeViews); - - for (ASDisplayNode *node in self.subnodes) { - [self _addSubnodeSubviewOrSublayer:node]; - } -} - -- (void)_addSubnodeSubviewOrSublayer:(ASDisplayNode *)subnode -{ - ASDisplayNodeAssertMainThread(); - - // Due to a bug in Apple's framework we have to use the layer index to insert a subview - // so just use the count of the sublayers to add the subnode - NSInteger idx = _layer.sublayers.count; // No locking is needed as it's main thread only - [self _insertSubnodeSubviewOrSublayer:subnode atIndex:idx]; -} - -- (void)replaceSubnode:(ASDisplayNode *)oldSubnode withSubnode:(ASDisplayNode *)replacementSubnode -{ - ASDisplayNodeLogEvent(self, @"replaceSubnode: %@ withSubnode: %@ with automaticallyManagesSubnodes: %@", - oldSubnode, replacementSubnode, self.automaticallyManagesSubnodes ? @"YES" : @"NO"); - [self _replaceSubnode:oldSubnode withSubnode:replacementSubnode]; -} - -- (void)_replaceSubnode:(ASDisplayNode *)oldSubnode withSubnode:(ASDisplayNode *)replacementSubnode -{ - ASDisplayNodeAssertThreadAffinity(self); - - if (replacementSubnode == nil) { - ASDisplayNodeFailAssert(@"Invalid subnode to replace"); - return; - } - - if (oldSubnode.supernode != self) { - ASDisplayNodeFailAssert(@"Old Subnode to replace must be a subnode"); - return; - } - - ASDisplayNodeAssert(!(self.nodeLoaded && !oldSubnode.nodeLoaded), @"We have view loaded, but child node does not."); - - NSInteger subnodeIndex; - NSInteger sublayerIndex = NSNotFound; - { - ASDN::MutexLocker l(__instanceLock__); - ASDisplayNodeAssert(_subnodes, @"You should have subnodes if you have a subnode"); - - subnodeIndex = [_subnodes indexOfObjectIdenticalTo:oldSubnode]; - - // Don't bother figuring out the sublayerIndex if in a rasterized subtree, because there are no layers in the - // hierarchy and none of this could possibly work. - if (subtreeIsRasterized(self) == NO) { - if (_layer) { - sublayerIndex = [_layer.sublayers indexOfObjectIdenticalTo:oldSubnode.layer]; - ASDisplayNodeAssert(sublayerIndex != NSNotFound, @"Somehow oldSubnode's supernode is self, yet we could not find it in our layers to replace"); - if (sublayerIndex == NSNotFound) { - return; - } - } - } - } - - [self _insertSubnode:replacementSubnode atSubnodeIndex:subnodeIndex sublayerIndex:sublayerIndex andRemoveSubnode:oldSubnode]; -} - -- (void)insertSubnode:(ASDisplayNode *)subnode belowSubnode:(ASDisplayNode *)below -{ - ASDisplayNodeLogEvent(self, @"insertSubnode: %@ belowSubnode: %@ with automaticallyManagesSubnodes: %@", - subnode, below, self.automaticallyManagesSubnodes ? @"YES" : @"NO"); - [self _insertSubnode:subnode belowSubnode:below]; -} - -- (void)_insertSubnode:(ASDisplayNode *)subnode belowSubnode:(ASDisplayNode *)below -{ - ASDisplayNodeAssertThreadAffinity(self); - ASAssertUnlocked(__instanceLock__); - - if (subnode == nil) { - ASDisplayNodeFailAssert(@"Cannot insert a nil subnode"); - return; - } - - if (below.supernode != self) { - ASDisplayNodeFailAssert(@"Node to insert below must be a subnode"); - return; - } - - NSInteger belowSubnodeIndex; - NSInteger belowSublayerIndex = NSNotFound; - { - ASDN::MutexLocker l(__instanceLock__); - ASDisplayNodeAssert(_subnodes, @"You should have subnodes if you have a subnode"); - - belowSubnodeIndex = [_subnodes indexOfObjectIdenticalTo:below]; - - // Don't bother figuring out the sublayerIndex if in a rasterized subtree, because there are no layers in the - // hierarchy and none of this could possibly work. - if (subtreeIsRasterized(self) == NO) { - if (_layer) { - belowSublayerIndex = [_layer.sublayers indexOfObjectIdenticalTo:below.layer]; - ASDisplayNodeAssert(belowSublayerIndex != NSNotFound, @"Somehow below's supernode is self, yet we could not find it in our layers to reference"); - if (belowSublayerIndex == NSNotFound) - return; - } - - ASDisplayNodeAssert(belowSubnodeIndex != NSNotFound, @"Couldn't find above in subnodes"); - - // If the subnode is already in the subnodes array / sublayers and it's before the below node, removing it to - // insert it will mess up our calculation - if (subnode.supernode == self) { - NSInteger currentIndexInSubnodes = [_subnodes indexOfObjectIdenticalTo:subnode]; - if (currentIndexInSubnodes < belowSubnodeIndex) { - belowSubnodeIndex--; - } - if (_layer) { - NSInteger currentIndexInSublayers = [_layer.sublayers indexOfObjectIdenticalTo:subnode.layer]; - if (currentIndexInSublayers < belowSublayerIndex) { - belowSublayerIndex--; - } - } - } - } - } - - ASDisplayNodeAssert(belowSubnodeIndex != NSNotFound, @"Couldn't find below in subnodes"); - - [self _insertSubnode:subnode atSubnodeIndex:belowSubnodeIndex sublayerIndex:belowSublayerIndex andRemoveSubnode:nil]; -} - -- (void)insertSubnode:(ASDisplayNode *)subnode aboveSubnode:(ASDisplayNode *)above -{ - ASDisplayNodeLogEvent(self, @"insertSubnode: %@ abodeSubnode: %@ with automaticallyManagesSubnodes: %@", - subnode, above, self.automaticallyManagesSubnodes ? @"YES" : @"NO"); - [self _insertSubnode:subnode aboveSubnode:above]; -} - -- (void)_insertSubnode:(ASDisplayNode *)subnode aboveSubnode:(ASDisplayNode *)above -{ - ASDisplayNodeAssertThreadAffinity(self); - ASAssertUnlocked(__instanceLock__); - - if (subnode == nil) { - ASDisplayNodeFailAssert(@"Cannot insert a nil subnode"); - return; - } - - if (above.supernode != self) { - ASDisplayNodeFailAssert(@"Node to insert above must be a subnode"); - return; - } - - NSInteger aboveSubnodeIndex; - NSInteger aboveSublayerIndex = NSNotFound; - { - ASDN::MutexLocker l(__instanceLock__); - ASDisplayNodeAssert(_subnodes, @"You should have subnodes if you have a subnode"); - - aboveSubnodeIndex = [_subnodes indexOfObjectIdenticalTo:above]; - - // Don't bother figuring out the sublayerIndex if in a rasterized subtree, because there are no layers in the - // hierarchy and none of this could possibly work. - if (subtreeIsRasterized(self) == NO) { - if (_layer) { - aboveSublayerIndex = [_layer.sublayers indexOfObjectIdenticalTo:above.layer]; - ASDisplayNodeAssert(aboveSublayerIndex != NSNotFound, @"Somehow above's supernode is self, yet we could not find it in our layers to replace"); - if (aboveSublayerIndex == NSNotFound) - return; - } - - ASDisplayNodeAssert(aboveSubnodeIndex != NSNotFound, @"Couldn't find above in subnodes"); - - // If the subnode is already in the subnodes array / sublayers and it's before the below node, removing it to - // insert it will mess up our calculation - if (subnode.supernode == self) { - NSInteger currentIndexInSubnodes = [_subnodes indexOfObjectIdenticalTo:subnode]; - if (currentIndexInSubnodes <= aboveSubnodeIndex) { - aboveSubnodeIndex--; - } - if (_layer) { - NSInteger currentIndexInSublayers = [_layer.sublayers indexOfObjectIdenticalTo:subnode.layer]; - if (currentIndexInSublayers <= aboveSublayerIndex) { - aboveSublayerIndex--; - } - } - } - } - } - - [self _insertSubnode:subnode atSubnodeIndex:incrementIfFound(aboveSubnodeIndex) sublayerIndex:incrementIfFound(aboveSublayerIndex) andRemoveSubnode:nil]; -} - -- (void)insertSubnode:(ASDisplayNode *)subnode atIndex:(NSInteger)idx -{ - ASDisplayNodeLogEvent(self, @"insertSubnode: %@ atIndex: %td with automaticallyManagesSubnodes: %@", - subnode, idx, self.automaticallyManagesSubnodes ? @"YES" : @"NO"); - [self _insertSubnode:subnode atIndex:idx]; -} - -- (void)_insertSubnode:(ASDisplayNode *)subnode atIndex:(NSInteger)idx -{ - ASDisplayNodeAssertThreadAffinity(self); - ASAssertUnlocked(__instanceLock__); - - if (subnode == nil) { - ASDisplayNodeFailAssert(@"Cannot insert a nil subnode"); - return; - } - - NSInteger sublayerIndex = NSNotFound; - { - ASDN::MutexLocker l(__instanceLock__); - - if (idx > _subnodes.count || idx < 0) { - ASDisplayNodeFailAssert(@"Cannot insert a subnode at index %ld. Count is %ld", (long)idx, (long)_subnodes.count); - return; - } - - // Don't bother figuring out the sublayerIndex if in a rasterized subtree, because there are no layers in the - // hierarchy and none of this could possibly work. - if (subtreeIsRasterized(self) == NO) { - // Account for potentially having other subviews - if (_layer && idx == 0) { - sublayerIndex = 0; - } else if (_layer) { - ASDisplayNode *positionInRelationTo = (_subnodes.count > 0 && idx > 0) ? _subnodes[idx - 1] : nil; - if (positionInRelationTo) { - sublayerIndex = incrementIfFound([_layer.sublayers indexOfObjectIdenticalTo:positionInRelationTo.layer]); - } - } - } - } - - [self _insertSubnode:subnode atSubnodeIndex:idx sublayerIndex:sublayerIndex andRemoveSubnode:nil]; -} - -- (void)_removeSubnode:(ASDisplayNode *)subnode -{ - ASDisplayNodeAssertThreadAffinity(self); - ASAssertUnlocked(__instanceLock__); - - // Don't call self.supernode here because that will retain/autorelease the supernode. This method -_removeSupernode: is often called while tearing down a node hierarchy, and the supernode in question might be in the middle of its -dealloc. The supernode is never messaged, only compared by value, so this is safe. - // The particular issue that triggers this edge case is when a node calls -removeFromSupernode on a subnode from within its own -dealloc method. - if (!subnode || subnode.supernode != self) { - return; - } - - __instanceLock__.lock(); - [_subnodes removeObjectIdenticalTo:subnode]; - _cachedSubnodes = nil; - __instanceLock__.unlock(); - - [subnode _setSupernode:nil]; -} - -- (void)removeFromSupernode -{ - ASDisplayNodeLogEvent(self, @"removeFromSupernode with automaticallyManagesSubnodes: %@", - self.automaticallyManagesSubnodes ? @"YES" : @"NO"); - [self _removeFromSupernode]; -} - -- (void)_removeFromSupernode -{ - ASDisplayNodeAssertThreadAffinity(self); - ASAssertUnlocked(__instanceLock__); - - __instanceLock__.lock(); - __weak ASDisplayNode *supernode = _supernode; - __weak UIView *view = _view; - __weak CALayer *layer = _layer; - __instanceLock__.unlock(); - - [self _removeFromSupernode:supernode view:view layer:layer]; -} - -- (void)_removeFromSupernodeIfEqualTo:(ASDisplayNode *)supernode -{ - ASDisplayNodeAssertThreadAffinity(self); - ASAssertUnlocked(__instanceLock__); - - __instanceLock__.lock(); - - // Only remove if supernode is still the expected supernode - if (!ASObjectIsEqual(_supernode, supernode)) { - __instanceLock__.unlock(); - return; - } - - __weak UIView *view = _view; - __weak CALayer *layer = _layer; - __instanceLock__.unlock(); - - [self _removeFromSupernode:supernode view:view layer:layer]; -} - -- (void)_removeFromSupernode:(ASDisplayNode *)supernode view:(UIView *)view layer:(CALayer *)layer -{ - // Note: we continue even if supernode is nil to ensure view/layer are removed from hierarchy. - - if (supernode != nil) { - as_log_verbose(ASNodeLog(), "Remove %@ from supernode %@", self, supernode); - } - - // Clear supernode's reference to us before removing the view from the hierarchy, as _ASDisplayView - // will trigger us to clear our _supernode pointer in willMoveToSuperview:nil. - // This may result in removing the last strong reference, triggering deallocation after this method. - [supernode _removeSubnode:self]; - - if (view != nil) { - [view removeFromSuperview]; - } else if (layer != nil) { - [layer removeFromSuperlayer]; - } -} - -#pragma mark - Visibility API - -- (BOOL)__visibilityNotificationsDisabled -{ - // Currently, this method is only used by the testing infrastructure to verify this internal feature. - ASDN::MutexLocker l(__instanceLock__); - return _flags.visibilityNotificationsDisabled > 0; -} - -- (BOOL)__selfOrParentHasVisibilityNotificationsDisabled -{ - ASDN::MutexLocker l(__instanceLock__); - return (_hierarchyState & ASHierarchyStateTransitioningSupernodes); -} - -- (void)__incrementVisibilityNotificationsDisabled -{ - __instanceLock__.lock(); - const size_t maxVisibilityIncrement = (1ULL< 0, @"Can't decrement past 0"); - if (_flags.visibilityNotificationsDisabled > 0) { - _flags.visibilityNotificationsDisabled--; - } - BOOL visibilityNotificationsDisabled = (_flags.visibilityNotificationsDisabled == 0); - __instanceLock__.unlock(); - - if (visibilityNotificationsDisabled) { - // Must have just transitioned from 1 to 0. Notify all subnodes that we are no longer in a disabled state. - // FIXME: This system should be revisited when refactoring and consolidating the implementation of the - // addSubnode: and insertSubnode:... methods. As implemented, though logically irrelevant for expected use cases, - // multiple nodes in the subtree below may have a non-zero visibilityNotification count and still have - // the ASHierarchyState bit cleared (the only value checked when reading this state). - [self exitHierarchyState:ASHierarchyStateTransitioningSupernodes]; - } -} - -#pragma mark - Placeholder - -- (void)_locked_layoutPlaceholderIfNecessary -{ - ASAssertLocked(__instanceLock__); - if ([self _locked_shouldHavePlaceholderLayer]) { - [self _locked_setupPlaceholderLayerIfNeeded]; - } - // Update the placeholderLayer size in case the node size has changed since the placeholder was added. - _placeholderLayer.frame = self.threadSafeBounds; -} - -- (BOOL)_locked_shouldHavePlaceholderLayer -{ - ASAssertLocked(__instanceLock__); - return (_placeholderEnabled && [self _implementsDisplay]); -} - -- (void)_locked_setupPlaceholderLayerIfNeeded -{ - ASDisplayNodeAssertMainThread(); - ASAssertLocked(__instanceLock__); - - if (!_placeholderLayer) { - _placeholderLayer = [CALayer layer]; - // do not set to CGFLOAT_MAX in the case that something needs to be overtop the placeholder - _placeholderLayer.zPosition = 9999.0; - } - - if (_placeholderLayer.contents == nil) { - if (!_placeholderImage) { - _placeholderImage = [self placeholderImage]; - } - if (_placeholderImage) { - BOOL stretchable = !UIEdgeInsetsEqualToEdgeInsets(_placeholderImage.capInsets, UIEdgeInsetsZero); - if (stretchable) { - ASDisplayNodeSetResizableContents(_placeholderLayer, _placeholderImage); - } else { - _placeholderLayer.contentsScale = self.contentsScale; - _placeholderLayer.contents = (id)_placeholderImage.CGImage; - } - } - } -} - -- (UIImage *)placeholderImage -{ - // Subclass hook - return nil; -} - -- (BOOL)placeholderShouldPersist -{ - // Subclass hook - return NO; -} - -#pragma mark - Hierarchy State - -- (BOOL)isInHierarchy -{ - ASDN::MutexLocker l(__instanceLock__); - return _flags.isInHierarchy; -} - -- (void)__enterHierarchy -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"Should not cause recursive __enterHierarchy"); - ASAssertUnlocked(__instanceLock__); - ASDisplayNodeLogEvent(self, @"enterHierarchy"); - - // Profiling has shown that locking this method is beneficial, so each of the property accesses don't have to lock and unlock. - __instanceLock__.lock(); - - if (!_flags.isInHierarchy && !_flags.visibilityNotificationsDisabled && ![self __selfOrParentHasVisibilityNotificationsDisabled]) { - _flags.isEnteringHierarchy = YES; - _flags.isInHierarchy = YES; - - // Don't call -willEnterHierarchy while holding __instanceLock__. - // This method and subsequent ones (i.e -interfaceState and didEnter(.*)State) - // don't expect that they are called while the lock is being held. - // More importantly, didEnter(.*)State methods are meant to be overriden by clients. - // And so they can potentially walk up the node tree and cause deadlocks, or do expensive tasks and cause the lock to be held for too long. - __instanceLock__.unlock(); - [self willEnterHierarchy]; - for (ASDisplayNode *subnode in self.subnodes) { - [subnode __enterHierarchy]; - } - __instanceLock__.lock(); - - _flags.isEnteringHierarchy = NO; - - // If we don't have contents finished drawing by the time we are on screen, immediately add the placeholder (if it is enabled and we do have something to draw). - if (self.contents == nil && [self _implementsDisplay]) { - CALayer *layer = self.layer; - [layer setNeedsDisplay]; - - if ([self _locked_shouldHavePlaceholderLayer]) { - [CATransaction begin]; - [CATransaction setDisableActions:YES]; - [self _locked_setupPlaceholderLayerIfNeeded]; - _placeholderLayer.opacity = 1.0; - [CATransaction commit]; - [layer addSublayer:_placeholderLayer]; - } - } - } - - __instanceLock__.unlock(); - - [self didEnterHierarchy]; -} - -- (void)__exitHierarchy -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(!_flags.isExitingHierarchy, @"Should not cause recursive __exitHierarchy"); - ASAssertUnlocked(__instanceLock__); - ASDisplayNodeLogEvent(self, @"exitHierarchy"); - - // Profiling has shown that locking this method is beneficial, so each of the property accesses don't have to lock and unlock. - __instanceLock__.lock(); - - if (_flags.isInHierarchy && !_flags.visibilityNotificationsDisabled && ![self __selfOrParentHasVisibilityNotificationsDisabled]) { - _flags.isExitingHierarchy = YES; - _flags.isInHierarchy = NO; - - // Don't call -didExitHierarchy while holding __instanceLock__. - // This method and subsequent ones (i.e -interfaceState and didExit(.*)State) - // don't expect that they are called while the lock is being held. - // More importantly, didExit(.*)State methods are meant to be overriden by clients. - // And so they can potentially walk up the node tree and cause deadlocks, or do expensive tasks and cause the lock to be held for too long. - __instanceLock__.unlock(); - [self didExitHierarchy]; - for (ASDisplayNode *subnode in self.subnodes) { - [subnode __exitHierarchy]; - } - __instanceLock__.lock(); - - _flags.isExitingHierarchy = NO; - } - - __instanceLock__.unlock(); -} - -- (void)enterHierarchyState:(ASHierarchyState)hierarchyState -{ - if (hierarchyState == ASHierarchyStateNormal) { - return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing. - } - - ASDisplayNodePerformBlockOnEveryNode(nil, self, NO, ^(ASDisplayNode *node) { - node.hierarchyState |= hierarchyState; - }); -} - -- (void)exitHierarchyState:(ASHierarchyState)hierarchyState -{ - if (hierarchyState == ASHierarchyStateNormal) { - return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing. - } - ASDisplayNodePerformBlockOnEveryNode(nil, self, NO, ^(ASDisplayNode *node) { - node.hierarchyState &= (~hierarchyState); - }); -} - -- (ASHierarchyState)hierarchyState -{ - ASDN::MutexLocker l(__instanceLock__); - return _hierarchyState; -} - -- (void)setHierarchyState:(ASHierarchyState)newState -{ - ASHierarchyState oldState = ASHierarchyStateNormal; - { - ASDN::MutexLocker l(__instanceLock__); - if (_hierarchyState == newState) { - return; - } - oldState = _hierarchyState; - _hierarchyState = newState; - } - - // Entered rasterization state. - if (newState & ASHierarchyStateRasterized) { - ASDisplayNodeAssert(checkFlag(Synchronous) == NO, @"Node created using -initWithViewBlock:/-initWithLayerBlock: cannot be added to subtree of node with subtree rasterization enabled. Node: %@", self); - } - - // Entered or exited range managed state. - if ((newState & ASHierarchyStateRangeManaged) != (oldState & ASHierarchyStateRangeManaged)) { - if (newState & ASHierarchyStateRangeManaged) { - [self enterInterfaceState:self.supernode.pendingInterfaceState]; - } else { - // The case of exiting a range-managed state should be fairly rare. Adding or removing the node - // to a view hierarchy will cause its interfaceState to be either fully set or unset (all fields), - // but because we might be about to be added to a view hierarchy, exiting the interface state now - // would cause inefficient churn. The tradeoff is that we may not clear contents / fetched data - // for nodes that are removed from a managed state and then retained but not used (bad idea anyway!) - } - } - - if ((newState & ASHierarchyStateLayoutPending) != (oldState & ASHierarchyStateLayoutPending)) { - if (newState & ASHierarchyStateLayoutPending) { - // Entering layout pending state - } else { - // Leaving layout pending state, reset related properties - ASDN::MutexLocker l(__instanceLock__); - _pendingTransitionID = ASLayoutElementContextInvalidTransitionID; - _pendingLayoutTransition = nil; - } - } - - ASDisplayNodeLogEvent(self, @"setHierarchyState: %@", NSStringFromASHierarchyStateChange(oldState, newState)); - as_log_verbose(ASNodeLog(), "%s%@ %@", sel_getName(_cmd), NSStringFromASHierarchyStateChange(oldState, newState), self); -} - -- (void)willEnterHierarchy -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(_flags.isEnteringHierarchy, @"You should never call -willEnterHierarchy directly. Appearance is automatically managed by ASDisplayNode"); - ASDisplayNodeAssert(!_flags.isExitingHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive"); - ASAssertUnlocked(__instanceLock__); - - if (![self supportsRangeManagedInterfaceState]) { - self.interfaceState = ASInterfaceStateInHierarchy; - } else if (ASCATransactionQueue.sharedQueue.isEnabled) { - __instanceLock__.lock(); - ASInterfaceState state = _preExitingInterfaceState; - _preExitingInterfaceState = ASInterfaceStateNone; - __instanceLock__.unlock(); - // Layer thrash happened, revert to before exiting. - if (state != ASInterfaceStateNone) { - self.interfaceState = state; - } - } -} - -- (void)didEnterHierarchy { - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"You should never call -didEnterHierarchy directly. Appearance is automatically managed by ASDisplayNode"); - ASDisplayNodeAssert(!_flags.isExitingHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive"); - ASDisplayNodeAssert(_flags.isInHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive"); - ASAssertUnlocked(__instanceLock__); -} - -- (void)didExitHierarchy -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(_flags.isExitingHierarchy, @"You should never call -didExitHierarchy directly. Appearance is automatically managed by ASDisplayNode"); - ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive"); - ASAssertUnlocked(__instanceLock__); - - // This case is important when tearing down hierarchies. We must deliver a visibileStateDidChange:NO callback, as part our API guarantee that this method can be used for - // things like data analytics about user content viewing. We cannot call the method in the dealloc as any incidental retain operations in client code would fail. - // Additionally, it may be that a Standard UIView which is containing us is moving between hierarchies, and we should not send the call if we will be re-added in the - // same runloop. Strategy: strong reference (might be the last!), wait one runloop, and confirm we are still outside the hierarchy (both layer-backed and view-backed). - // TODO: This approach could be optimized by only performing the dispatch for root elements + recursively apply the interface state change. This would require a closer - // integration with _ASDisplayLayer to ensure that the superlayer pointer has been cleared by this stage (to check if we are root or not), or a different delegate call. - -#if !ENABLE_NEW_EXIT_HIERARCHY_BEHAVIOR - if (![self supportsRangeManagedInterfaceState]) { - self.interfaceState = ASInterfaceStateNone; - return; - } -#endif - if (ASInterfaceStateIncludesVisible(self.pendingInterfaceState)) { - void(^exitVisibleInterfaceState)(void) = ^{ - // This block intentionally retains self. - __instanceLock__.lock(); - unsigned isStillInHierarchy = _flags.isInHierarchy; - BOOL isVisible = ASInterfaceStateIncludesVisible(_pendingInterfaceState); - ASInterfaceState newState = (_pendingInterfaceState & ~ASInterfaceStateVisible); - // layer may be thrashed, we need to remember the state so we can reset if it enters in same runloop later. - _preExitingInterfaceState = _pendingInterfaceState; - __instanceLock__.unlock(); - if (!isStillInHierarchy && isVisible) { -#if ENABLE_NEW_EXIT_HIERARCHY_BEHAVIOR - if (![self supportsRangeManagedInterfaceState]) { - newState = ASInterfaceStateNone; - } -#endif - self.interfaceState = newState; - } - }; - - if (!ASCATransactionQueue.sharedQueue.enabled) { - dispatch_async(dispatch_get_main_queue(), exitVisibleInterfaceState); - } else { - exitVisibleInterfaceState(); - } - } -} - -#pragma mark - Interface State - -/** - * We currently only set interface state on nodes in table/collection views. For other nodes, if they are - * in the hierarchy we enable all ASInterfaceState types with `ASInterfaceStateInHierarchy`, otherwise `None`. - */ -- (BOOL)supportsRangeManagedInterfaceState -{ - ASDN::MutexLocker l(__instanceLock__); - return ASHierarchyStateIncludesRangeManaged(_hierarchyState); -} - -- (void)enterInterfaceState:(ASInterfaceState)interfaceState -{ - if (interfaceState == ASInterfaceStateNone) { - return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing. - } - ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode *node) { - node.interfaceState |= interfaceState; - }); -} - -- (void)exitInterfaceState:(ASInterfaceState)interfaceState -{ - if (interfaceState == ASInterfaceStateNone) { - return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing. - } - ASDisplayNodeLogEvent(self, @"%s %@", sel_getName(_cmd), NSStringFromASInterfaceState(interfaceState)); - ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode *node) { - node.interfaceState &= (~interfaceState); - }); -} - -- (void)recursivelySetInterfaceState:(ASInterfaceState)newInterfaceState -{ - as_activity_create_for_scope("Recursively set interface state"); - - // Instead of each node in the recursion assuming it needs to schedule itself for display, - // setInterfaceState: skips this when handling range-managed nodes (our whole subtree has this set). - // If our range manager intends for us to be displayed right now, and didn't before, get started! - BOOL shouldScheduleDisplay = [self supportsRangeManagedInterfaceState] && [self shouldScheduleDisplayWithNewInterfaceState:newInterfaceState]; - ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode *node) { - node.interfaceState = newInterfaceState; - }); - if (shouldScheduleDisplay) { - [ASDisplayNode scheduleNodeForRecursiveDisplay:self]; - } -} - -- (ASInterfaceState)interfaceState -{ - ASDN::MutexLocker l(__instanceLock__); - return _interfaceState; -} - -- (void)setInterfaceState:(ASInterfaceState)newState -{ - if (!ASCATransactionQueue.sharedQueue.enabled) { - [self applyPendingInterfaceState:newState]; - } else { - ASDN::MutexLocker l(__instanceLock__); - if (_pendingInterfaceState != newState) { - _pendingInterfaceState = newState; - [[ASCATransactionQueue sharedQueue] enqueue:self]; - } - } -} - -- (ASInterfaceState)pendingInterfaceState -{ - ASDN::MutexLocker l(__instanceLock__); - return _pendingInterfaceState; -} - -- (void)applyPendingInterfaceState:(ASInterfaceState)newPendingState -{ - //This method is currently called on the main thread. The assert has been added here because all of the - //did(Enter|Exit)(Display|Visible|Preload)State methods currently guarantee calling on main. - ASDisplayNodeAssertMainThread(); - - // This method manages __instanceLock__ itself, to ensure the lock is not held while didEnter/Exit(.*)State methods are called, thus avoid potential deadlocks - ASAssertUnlocked(__instanceLock__); - - ASInterfaceState oldState = ASInterfaceStateNone; - ASInterfaceState newState = ASInterfaceStateNone; - { - ASDN::MutexLocker l(__instanceLock__); - // newPendingState will not be used when ASCATransactionQueue is enabled - // and use _pendingInterfaceState instead for interfaceState update. - if (!ASCATransactionQueue.sharedQueue.enabled) { - _pendingInterfaceState = newPendingState; - } - oldState = _interfaceState; - newState = _pendingInterfaceState; - if (newState == oldState) { - return; - } - _interfaceState = newState; - _preExitingInterfaceState = ASInterfaceStateNone; - } - - // It should never be possible for a node to be visible but not be allowed / expected to display. - ASDisplayNodeAssertFalse(ASInterfaceStateIncludesVisible(newState) && !ASInterfaceStateIncludesDisplay(newState)); - - // TODO: Trigger asynchronous measurement if it is not already cached or being calculated. - // if ((newState & ASInterfaceStateMeasureLayout) != (oldState & ASInterfaceStateMeasureLayout)) { - // } - - // For the Preload and Display ranges, we don't want to call -clear* if not being managed by a range controller. - // Otherwise we get flashing behavior from normal UIKit manipulations like navigation controller push / pop. - // Still, the interfaceState should be updated to the current state of the node; just don't act on the transition. - - // Entered or exited data loading state. - BOOL nowPreload = ASInterfaceStateIncludesPreload(newState); - BOOL wasPreload = ASInterfaceStateIncludesPreload(oldState); - - if (nowPreload != wasPreload) { - if (nowPreload) { - [self didEnterPreloadState]; - } else { - // We don't want to call -didExitPreloadState on nodes that aren't being managed by a range controller. - // Otherwise we get flashing behavior from normal UIKit manipulations like navigation controller push / pop. - if ([self supportsRangeManagedInterfaceState]) { - [self didExitPreloadState]; - } - } - } - - // Entered or exited contents rendering state. - BOOL nowDisplay = ASInterfaceStateIncludesDisplay(newState); - BOOL wasDisplay = ASInterfaceStateIncludesDisplay(oldState); - - if (nowDisplay != wasDisplay) { - if ([self supportsRangeManagedInterfaceState]) { - if (nowDisplay) { - // Once the working window is eliminated (ASRangeHandlerRender), trigger display directly here. - [self setDisplaySuspended:NO]; - } else { - [self setDisplaySuspended:YES]; - //schedule clear contents on next runloop - dispatch_async(dispatch_get_main_queue(), ^{ - __instanceLock__.lock(); - ASInterfaceState interfaceState = _interfaceState; - __instanceLock__.unlock(); - if (ASInterfaceStateIncludesDisplay(interfaceState) == NO) { - [self clearContents]; - } - }); - } - } else { - // NOTE: This case isn't currently supported as setInterfaceState: isn't exposed externally, and all - // internal use cases are range-managed. When a node is visible, don't mess with display - CA will start it. - if (!ASInterfaceStateIncludesVisible(newState)) { - // Check _implementsDisplay purely for efficiency - it's faster even than calling -asyncLayer. - if ([self _implementsDisplay]) { - if (nowDisplay) { - [ASDisplayNode scheduleNodeForRecursiveDisplay:self]; - } else { - [[self asyncLayer] cancelAsyncDisplay]; - //schedule clear contents on next runloop - dispatch_async(dispatch_get_main_queue(), ^{ - __instanceLock__.lock(); - ASInterfaceState interfaceState = _interfaceState; - __instanceLock__.unlock(); - if (ASInterfaceStateIncludesDisplay(interfaceState) == NO) { - [self clearContents]; - } - }); - } - } - } - } - - if (nowDisplay) { - [self didEnterDisplayState]; - } else { - [self didExitDisplayState]; - } - } - - // Became visible or invisible. When range-managed, this represents literal visibility - at least one pixel - // is onscreen. If not range-managed, we can't guarantee more than the node being present in an onscreen window. - BOOL nowVisible = ASInterfaceStateIncludesVisible(newState); - BOOL wasVisible = ASInterfaceStateIncludesVisible(oldState); - - if (nowVisible != wasVisible) { - if (nowVisible) { - [self didEnterVisibleState]; - } else { - [self didExitVisibleState]; - } - } - - // Log this change, unless it's just the node going from {} -> {Measure} because that change happens - // for all cell nodes and it isn't currently meaningful. - BOOL measureChangeOnly = ((oldState | newState) == ASInterfaceStateMeasureLayout); - if (!measureChangeOnly) { - as_log_verbose(ASNodeLog(), "%s %@ %@", sel_getName(_cmd), NSStringFromASInterfaceStateChange(oldState, newState), self); - } - - ASDisplayNodeLogEvent(self, @"interfaceStateDidChange: %@", NSStringFromASInterfaceStateChange(oldState, newState)); - [self interfaceStateDidChange:newState fromState:oldState]; -} - -- (void)prepareForCATransactionCommit -{ - // Apply _pendingInterfaceState actual _interfaceState, note that ASInterfaceStateNone is not used. - [self applyPendingInterfaceState:ASInterfaceStateNone]; -} - -- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState -{ - // Subclass hook - ASAssertUnlocked(__instanceLock__); - ASDisplayNodeAssertMainThread(); - [self enumerateInterfaceStateDelegates:^(id del) { - [del interfaceStateDidChange:newState fromState:oldState]; - }]; -} - -- (BOOL)shouldScheduleDisplayWithNewInterfaceState:(ASInterfaceState)newInterfaceState -{ - BOOL willDisplay = ASInterfaceStateIncludesDisplay(newInterfaceState); - BOOL nowDisplay = ASInterfaceStateIncludesDisplay(self.interfaceState); - return willDisplay && (willDisplay != nowDisplay); -} - -- (void)addInterfaceStateDelegate:(id )interfaceStateDelegate -{ - ASDN::MutexLocker l(__instanceLock__); - _hasHadInterfaceStateDelegates = YES; - for (int i = 0; i < AS_MAX_INTERFACE_STATE_DELEGATES; i++) { - if (_interfaceStateDelegates[i] == nil) { - _interfaceStateDelegates[i] = interfaceStateDelegate; - return; - } - } - ASDisplayNodeFailAssert(@"Exceeded interface state delegate limit: %d", AS_MAX_INTERFACE_STATE_DELEGATES); -} - -- (void)removeInterfaceStateDelegate:(id )interfaceStateDelegate -{ - ASDN::MutexLocker l(__instanceLock__); - for (int i = 0; i < AS_MAX_INTERFACE_STATE_DELEGATES; i++) { - if (_interfaceStateDelegates[i] == interfaceStateDelegate) { - _interfaceStateDelegates[i] = nil; - break; - } - } -} - -- (BOOL)isVisible -{ - ASDN::MutexLocker l(__instanceLock__); - return ASInterfaceStateIncludesVisible(_interfaceState); -} - -- (void)didEnterVisibleState -{ - // subclass override - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - [self enumerateInterfaceStateDelegates:^(id del) { - [del didEnterVisibleState]; - }]; -#if AS_ENABLE_TIPS - [ASTipsController.shared nodeDidAppear:self]; -#endif -} - -- (void)didExitVisibleState -{ - // subclass override - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - [self enumerateInterfaceStateDelegates:^(id del) { - [del didExitVisibleState]; - }]; -} - -- (BOOL)isInDisplayState -{ - ASDN::MutexLocker l(__instanceLock__); - return ASInterfaceStateIncludesDisplay(_interfaceState); -} - -- (void)didEnterDisplayState -{ - // subclass override - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - [self enumerateInterfaceStateDelegates:^(id del) { - [del didEnterDisplayState]; - }]; -} - -- (void)didExitDisplayState -{ - // subclass override - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - [self enumerateInterfaceStateDelegates:^(id del) { - [del didExitDisplayState]; - }]; -} - -- (BOOL)isInPreloadState -{ - ASDN::MutexLocker l(__instanceLock__); - return ASInterfaceStateIncludesPreload(_interfaceState); -} - -- (void)setNeedsPreload -{ - if (self.isInPreloadState) { - [self recursivelyPreload]; - } -} - -- (void)recursivelyPreload -{ - ASPerformBlockOnMainThread(^{ - ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode * _Nonnull node) { - [node didEnterPreloadState]; - }); - }); -} - -- (void)recursivelyClearPreloadedData -{ - ASPerformBlockOnMainThread(^{ - ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode * _Nonnull node) { - [node didExitPreloadState]; - }); - }); -} - -- (void)didEnterPreloadState -{ - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - - // If this node has ASM enabled and is not yet visible, force a layout pass to apply its applicable pending layout, if any, - // so that its subnodes are inserted/deleted and start preloading right away. - // - // - If it has an up-to-date layout (and subnodes), calling -layoutIfNeeded will be fast. - // - // - If it doesn't have a calculated or pending layout that fits its current bounds, a measurement pass will occur - // (see -__layout and -_u_measureNodeWithBoundsIfNecessary:). This scenario is uncommon, - // and running a measurement pass here is a fine trade-off because preloading any time after this point would be late. - if (self.automaticallyManagesSubnodes) { - [self layoutIfNeeded]; - } - [self enumerateInterfaceStateDelegates:^(id del) { - [del didEnterPreloadState]; - }]; -} - -- (void)didExitPreloadState -{ - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - [self enumerateInterfaceStateDelegates:^(id del) { - [del didExitPreloadState]; - }]; -} - -- (void)clearContents -{ - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - - ASDN::MutexLocker l(__instanceLock__); - if (_flags.canClearContentsOfLayer) { - // No-op if these haven't been created yet, as that guarantees they don't have contents that needs to be released. - _layer.contents = nil; - } - - _placeholderLayer.contents = nil; - _placeholderImage = nil; -} - -- (void)recursivelyClearContents -{ - ASPerformBlockOnMainThread(^{ - ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode * _Nonnull node) { - [node clearContents]; - }); - }); -} - -- (void)enumerateInterfaceStateDelegates:(void (NS_NOESCAPE ^)(id))block -{ - ASAssertUnlocked(__instanceLock__); - - id dels[AS_MAX_INTERFACE_STATE_DELEGATES]; - int count = 0; - { - ASLockScopeSelf(); - // Fast path for non-delegating nodes. - if (!_hasHadInterfaceStateDelegates) { - return; - } - - for (int i = 0; i < AS_MAX_INTERFACE_STATE_DELEGATES; i++) { - if ((dels[count] = _interfaceStateDelegates[i])) { - count++; - } - } - } - for (int i = 0; i < count; i++) { - block(dels[i]); - } -} - -#pragma mark - Gesture Recognizing - -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event -{ - // Subclass hook -} - -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event -{ - // Subclass hook -} - -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event -{ - // Subclass hook -} - -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event -{ - // Subclass hook -} - -- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer -{ - // This method is only implemented on UIView on iOS 6+. - ASDisplayNodeAssertMainThread(); - - // No locking needed as it's main thread only - UIView *view = _view; - if (view == nil) { - return YES; - } - - // If we reach the base implementation, forward up the view hierarchy. - UIView *superview = view.superview; - return [superview gestureRecognizerShouldBegin:gestureRecognizer]; -} - -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - return [_view hitTest:point withEvent:event]; -} - -- (void)setHitTestSlop:(UIEdgeInsets)hitTestSlop -{ - ASDN::MutexLocker l(__instanceLock__); - _hitTestSlop = hitTestSlop; -} - -- (UIEdgeInsets)hitTestSlop -{ - ASDN::MutexLocker l(__instanceLock__); - return _hitTestSlop; -} - -- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - UIEdgeInsets slop = self.hitTestSlop; - if (_view && UIEdgeInsetsEqualToEdgeInsets(slop, UIEdgeInsetsZero)) { - // Safer to use UIView's -pointInside:withEvent: if we can. - return [_view pointInside:point withEvent:event]; - } else { - return CGRectContainsPoint(UIEdgeInsetsInsetRect(self.bounds, slop), point); - } -} - - -#pragma mark - Pending View State - -- (void)_locked_applyPendingStateToViewOrLayer -{ - ASDisplayNodeAssertMainThread(); - ASAssertLocked(__instanceLock__); - ASDisplayNodeAssert(self.nodeLoaded, @"must have a view or layer"); - - TIME_SCOPED(_debugTimeToApplyPendingState); - - // If no view/layer properties were set before the view/layer were created, _pendingViewState will be nil and the default values - // for the view/layer are still valid. - [self _locked_applyPendingViewState]; - - if (_flags.displaySuspended) { - self._locked_asyncLayer.displaySuspended = YES; - } - if (!_flags.displaysAsynchronously) { - self._locked_asyncLayer.displaysAsynchronously = NO; - } -} - -- (void)applyPendingViewState -{ - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - - ASDN::MutexLocker l(__instanceLock__); - // FIXME: Ideally we'd call this as soon as the node receives -setNeedsLayout - // but automatic subnode management would require us to modify the node tree - // in the background on a loaded node, which isn't currently supported. - if (_pendingViewState.hasSetNeedsLayout) { - // Need to unlock before calling setNeedsLayout to avoid deadlocks. - // MutexUnlocker will re-lock at the end of scope. - ASDN::MutexUnlocker u(__instanceLock__); - [self __setNeedsLayout]; - } - - [self _locked_applyPendingViewState]; -} - -- (void)_locked_applyPendingViewState -{ - ASDisplayNodeAssertMainThread(); - ASAssertLocked(__instanceLock__); - ASDisplayNodeAssert([self _locked_isNodeLoaded], @"Expected node to be loaded before applying pending state."); - - if (_flags.layerBacked) { - [_pendingViewState applyToLayer:_layer]; - } else { - BOOL specialPropertiesHandling = ASDisplayNodeNeedsSpecialPropertiesHandling(checkFlag(Synchronous), _flags.layerBacked); - [_pendingViewState applyToView:_view withSpecialPropertiesHandling:specialPropertiesHandling]; - } - - // _ASPendingState objects can add up very quickly when adding - // many nodes. This is especially an issue in large collection views - // and table views. This needs to be weighed against the cost of - // reallocing a _ASPendingState. So in range managed nodes we - // delete the pending state, otherwise we just clear it. - if (ASHierarchyStateIncludesRangeManaged(_hierarchyState)) { - _pendingViewState = nil; - } else { - [_pendingViewState clearChanges]; - } -} - -// This method has proved helpful in a few rare scenarios, similar to a category extension on UIView, but assumes knowledge of _ASDisplayView. -// It's considered private API for now and its use should not be encouraged. -- (ASDisplayNode *)_supernodeWithClass:(Class)supernodeClass checkViewHierarchy:(BOOL)checkViewHierarchy -{ - ASDisplayNode *supernode = self.supernode; - while (supernode) { - if ([supernode isKindOfClass:supernodeClass]) - return supernode; - supernode = supernode.supernode; - } - if (!checkViewHierarchy) { - return nil; - } - - UIView *view = self.view.superview; - while (view) { - ASDisplayNode *viewNode = ((_ASDisplayView *)view).asyncdisplaykit_node; - if (viewNode) { - if ([viewNode isKindOfClass:supernodeClass]) - return viewNode; - } - - view = view.superview; - } - - return nil; -} - -#pragma mark - Performance Measurement - -- (void)setMeasurementOptions:(ASDisplayNodePerformanceMeasurementOptions)measurementOptions -{ - ASDN::MutexLocker l(__instanceLock__); - _measurementOptions = measurementOptions; -} - -- (ASDisplayNodePerformanceMeasurementOptions)measurementOptions -{ - ASDN::MutexLocker l(__instanceLock__); - return _measurementOptions; -} - -- (ASDisplayNodePerformanceMeasurements)performanceMeasurements -{ - ASDN::MutexLocker l(__instanceLock__); - ASDisplayNodePerformanceMeasurements measurements = { .layoutSpecNumberOfPasses = -1, .layoutSpecTotalTime = NAN, .layoutComputationNumberOfPasses = -1, .layoutComputationTotalTime = NAN }; - if (_measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutSpec) { - measurements.layoutSpecNumberOfPasses = _layoutSpecNumberOfPasses; - measurements.layoutSpecTotalTime = _layoutSpecTotalTime; - } - if (_measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutComputation) { - measurements.layoutComputationNumberOfPasses = _layoutComputationNumberOfPasses; - measurements.layoutComputationTotalTime = _layoutComputationTotalTime; - } - return measurements; -} - -#pragma mark - Accessibility - -- (void)setIsAccessibilityContainer:(BOOL)isAccessibilityContainer -{ - ASDN::MutexLocker l(__instanceLock__); - _isAccessibilityContainer = isAccessibilityContainer; -} - -- (BOOL)isAccessibilityContainer -{ - ASDN::MutexLocker l(__instanceLock__); - return _isAccessibilityContainer; -} - -#pragma mark - Debugging (Private) - -#if ASEVENTLOG_ENABLE -- (ASEventLog *)eventLog -{ - return _eventLog; -} -#endif - -- (NSMutableArray *)propertiesForDescription -{ - NSMutableArray *result = [NSMutableArray array]; - ASPushMainThreadAssertionsDisabled(); - - NSString *debugName = self.debugName; - if (debugName.length > 0) { - [result addObject:@{ (id)kCFNull : ASStringWithQuotesIfMultiword(debugName) }]; - } - - NSString *axId = self.accessibilityIdentifier; - if (axId.length > 0) { - [result addObject:@{ (id)kCFNull : ASStringWithQuotesIfMultiword(axId) }]; - } - - ASPopMainThreadAssertionsDisabled(); - return result; -} - -- (NSMutableArray *)propertiesForDebugDescription -{ - NSMutableArray *result = [NSMutableArray array]; - - if (self.debugName.length > 0) { - [result addObject:@{ @"debugName" : ASStringWithQuotesIfMultiword(self.debugName)}]; - } - if (self.accessibilityIdentifier.length > 0) { - [result addObject:@{ @"axId": ASStringWithQuotesIfMultiword(self.accessibilityIdentifier) }]; - } - - CGRect windowFrame = [self _frameInWindow]; - if (CGRectIsNull(windowFrame) == NO) { - [result addObject:@{ @"frameInWindow" : [NSValue valueWithCGRect:windowFrame] }]; - } - - // Attempt to find view controller. - // Note that the convenience method asdk_associatedViewController has an assertion - // that it's run on main. Since this is a debug method, let's bypass the assertion - // and run up the chain ourselves. - if (_view != nil) { - for (UIResponder *responder in [_view asdk_responderChainEnumerator]) { - UIViewController *vc = ASDynamicCast(responder, UIViewController); - if (vc) { - [result addObject:@{ @"viewController" : ASObjectDescriptionMakeTiny(vc) }]; - break; - } - } - } - - if (_view != nil) { - [result addObject:@{ @"alpha" : @(_view.alpha) }]; - [result addObject:@{ @"frame" : [NSValue valueWithCGRect:_view.frame] }]; - } else if (_layer != nil) { - [result addObject:@{ @"alpha" : @(_layer.opacity) }]; - [result addObject:@{ @"frame" : [NSValue valueWithCGRect:_layer.frame] }]; - } else if (_pendingViewState != nil) { - [result addObject:@{ @"alpha" : @(_pendingViewState.alpha) }]; - [result addObject:@{ @"frame" : [NSValue valueWithCGRect:_pendingViewState.frame] }]; - } -#ifndef MINIMAL_ASDK - // Check supernode so that if we are a cell node we don't find self. - ASCellNode *cellNode = [self supernodeOfClass:[ASCellNode class] includingSelf:NO]; - if (cellNode != nil) { - [result addObject:@{ @"cellNode" : ASObjectDescriptionMakeTiny(cellNode) }]; - } -#endif - - [result addObject:@{ @"interfaceState" : NSStringFromASInterfaceState(self.interfaceState)} ]; - - if (_view != nil) { - [result addObject:@{ @"view" : ASObjectDescriptionMakeTiny(_view) }]; - } else if (_layer != nil) { - [result addObject:@{ @"layer" : ASObjectDescriptionMakeTiny(_layer) }]; - } else if (_viewClass != nil) { - [result addObject:@{ @"viewClass" : _viewClass }]; - } else if (_layerClass != nil) { - [result addObject:@{ @"layerClass" : _layerClass }]; - } else if (_viewBlock != nil) { - [result addObject:@{ @"viewBlock" : _viewBlock }]; - } else if (_layerBlock != nil) { - [result addObject:@{ @"layerBlock" : _layerBlock }]; - } - -#if TIME_DISPLAYNODE_OPS - NSString *creationTypeString = [NSString stringWithFormat:@"cr8:%.2lfms dl:%.2lfms ap:%.2lfms ad:%.2lfms", 1000 * _debugTimeToCreateView, 1000 * _debugTimeForDidLoad, 1000 * _debugTimeToApplyPendingState, 1000 * _debugTimeToAddSubnodeViews]; - [result addObject:@{ @"creationTypeString" : creationTypeString }]; -#endif - - return result; -} - -- (NSString *)description -{ - return ASObjectDescriptionMake(self, [self propertiesForDescription]); -} - -- (NSString *)debugDescription -{ - ASPushMainThreadAssertionsDisabled(); - let result = ASObjectDescriptionMake(self, [self propertiesForDebugDescription]); - ASPopMainThreadAssertionsDisabled(); - return result; -} - -// This should only be called for debugging. It's not thread safe and it doesn't assert. -// NOTE: Returns CGRectNull if the node isn't in a hierarchy. -- (CGRect)_frameInWindow -{ - if (self.isNodeLoaded == NO || self.isInHierarchy == NO) { - return CGRectNull; - } - - if (self.layerBacked) { - CALayer *rootLayer = _layer; - CALayer *nextLayer = nil; - while ((nextLayer = rootLayer.superlayer) != nil) { - rootLayer = nextLayer; - } - - return [_layer convertRect:self.threadSafeBounds toLayer:rootLayer]; - } else { - return [_view convertRect:self.threadSafeBounds toView:nil]; - } -} - -#pragma mark - Trait Collection Hooks - -- (void)asyncTraitCollectionDidChange -{ - // Subclass override -} -@end - -#pragma mark - ASDisplayNode (Debugging) - -@implementation ASDisplayNode (Debugging) - -+ (void)setShouldStoreUnflattenedLayouts:(BOOL)shouldStore -{ - storesUnflattenedLayouts.store(shouldStore); -} - -+ (BOOL)shouldStoreUnflattenedLayouts -{ - return storesUnflattenedLayouts.load(); -} - -- (ASLayout *)unflattenedCalculatedLayout -{ - ASDN::MutexLocker l(__instanceLock__); - return _unflattenedLayout; -} - -- (NSString *)displayNodeRecursiveDescription -{ - return [self _recursiveDescriptionHelperWithIndent:@""]; -} - -- (NSString *)_recursiveDescriptionHelperWithIndent:(NSString *)indent -{ - NSMutableString *subtree = [[[indent stringByAppendingString:self.debugDescription] stringByAppendingString:@"\n"] mutableCopy]; - for (ASDisplayNode *n in self.subnodes) { - [subtree appendString:[n _recursiveDescriptionHelperWithIndent:[indent stringByAppendingString:@" | "]]]; - } - return subtree; -} - -- (NSString *)detailedLayoutDescription -{ - ASPushMainThreadAssertionsDisabled(); - ASDN::MutexLocker l(__instanceLock__); - let props = [[NSMutableArray alloc] init]; - - [props addObject:@{ @"layoutVersion": @(_layoutVersion.load()) }]; - [props addObject:@{ @"bounds": [NSValue valueWithCGRect:self.bounds] }]; - - if (_calculatedDisplayNodeLayout.layout) { - [props addObject:@{ @"calculatedLayout": _calculatedDisplayNodeLayout.layout }]; - [props addObject:@{ @"calculatedVersion": @(_calculatedDisplayNodeLayout.version) }]; - [props addObject:@{ @"calculatedConstrainedSize" : NSStringFromASSizeRange(_calculatedDisplayNodeLayout.constrainedSize) }]; - if (_calculatedDisplayNodeLayout.requestedLayoutFromAbove) { - [props addObject:@{ @"calculatedRequestedLayoutFromAbove": @"YES" }]; - } - } - if (_pendingDisplayNodeLayout.layout) { - [props addObject:@{ @"pendingLayout": _pendingDisplayNodeLayout.layout }]; - [props addObject:@{ @"pendingVersion": @(_pendingDisplayNodeLayout.version) }]; - [props addObject:@{ @"pendingConstrainedSize" : NSStringFromASSizeRange(_pendingDisplayNodeLayout.constrainedSize) }]; - if (_pendingDisplayNodeLayout.requestedLayoutFromAbove) { - [props addObject:@{ @"pendingRequestedLayoutFromAbove": (id)kCFNull }]; - } - } - - ASPopMainThreadAssertionsDisabled(); - return ASObjectDescriptionMake(self, props); -} - -@end - -#pragma mark - ASDisplayNode UIKit / CA Categories - -// We use associated objects as a last resort if our view is not a _ASDisplayView ie it doesn't have the _node ivar to write to - -static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; - -@implementation UIView (ASDisplayNodeInternal) - -- (void)setAsyncdisplaykit_node:(ASDisplayNode *)node -{ - ASWeakProxy *weakProxy = [ASWeakProxy weakProxyWithTarget:node]; - objc_setAssociatedObject(self, ASDisplayNodeAssociatedNodeKey, weakProxy, OBJC_ASSOCIATION_RETAIN); // Weak reference to avoid cycle, since the node retains the view. -} - -- (ASDisplayNode *)asyncdisplaykit_node -{ - ASWeakProxy *weakProxy = objc_getAssociatedObject(self, ASDisplayNodeAssociatedNodeKey); - return weakProxy.target; -} - -@end - -@implementation CALayer (ASDisplayNodeInternal) - -- (void)setAsyncdisplaykit_node:(ASDisplayNode *)node -{ - ASWeakProxy *weakProxy = [ASWeakProxy weakProxyWithTarget:node]; - objc_setAssociatedObject(self, ASDisplayNodeAssociatedNodeKey, weakProxy, OBJC_ASSOCIATION_RETAIN); // Weak reference to avoid cycle, since the node retains the layer. -} - -- (ASDisplayNode *)asyncdisplaykit_node -{ - ASWeakProxy *weakProxy = objc_getAssociatedObject(self, ASDisplayNodeAssociatedNodeKey); - return weakProxy.target; -} - -@end - -@implementation UIView (AsyncDisplayKit) - -- (void)addSubnode:(ASDisplayNode *)subnode -{ - if (subnode.layerBacked) { - // Call -addSubnode: so that we use the asyncdisplaykit_node path if possible. - [self.layer addSubnode:subnode]; - } else { - ASDisplayNode *selfNode = self.asyncdisplaykit_node; - if (selfNode) { - [selfNode addSubnode:subnode]; - } else { - if (subnode.supernode) { - [subnode removeFromSupernode]; - } - [self addSubview:subnode.view]; - } - } -} - -@end - -@implementation CALayer (AsyncDisplayKit) - -- (void)addSubnode:(ASDisplayNode *)subnode -{ - ASDisplayNode *selfNode = self.asyncdisplaykit_node; - if (selfNode) { - [selfNode addSubnode:subnode]; - } else { - if (subnode.supernode) { - [subnode removeFromSupernode]; - } - [self addSublayer:subnode.layer]; - } -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNodeExtras.mm b/submodules/AsyncDisplayKit/Source/ASDisplayNodeExtras.mm index d1be1576e3..02871409ca 100644 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNodeExtras.mm +++ b/submodules/AsyncDisplayKit/Source/ASDisplayNodeExtras.mm @@ -8,9 +8,10 @@ // #import -#import +#import "ASDisplayNodeInternal.h" #import #import +#import #import #import diff --git a/submodules/AsyncDisplayKit/Source/Private/ASDisplayNodeInternal.h b/submodules/AsyncDisplayKit/Source/ASDisplayNodeInternal.h similarity index 97% rename from submodules/AsyncDisplayKit/Source/Private/ASDisplayNodeInternal.h rename to submodules/AsyncDisplayKit/Source/ASDisplayNodeInternal.h index 8cc49441e1..88b268c696 100644 --- a/submodules/AsyncDisplayKit/Source/Private/ASDisplayNodeInternal.h +++ b/submodules/AsyncDisplayKit/Source/ASDisplayNodeInternal.h @@ -16,7 +16,7 @@ #import #import #import -#import +#import "ASLayoutTransition.h" #import #import #import @@ -287,16 +287,6 @@ AS_EXTERN NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimest */ - (void)__setNeedsDisplay; -/** - * Setup the node -> controller reference. Strong or weak is based on - * the "shouldInvertStrongReference" property of the controller. - * - * Note: To prevent lock-ordering deadlocks, this method does not take the node's lock. - * In practice, changing the node controller of a node multiple times is not - * supported behavior. - */ -- (void)__setNodeController:(ASNodeController *)controller; - /** * Called whenever the node needs to layout its subnodes and, if it's already loaded, its subviews. Executes the layout pass for the node * diff --git a/submodules/AsyncDisplayKit/Source/Private/ASDisplayNodeLayout.h b/submodules/AsyncDisplayKit/Source/ASDisplayNodeLayout.h similarity index 98% rename from submodules/AsyncDisplayKit/Source/Private/ASDisplayNodeLayout.h rename to submodules/AsyncDisplayKit/Source/ASDisplayNodeLayout.h index ba8d9c273e..af905a813a 100644 --- a/submodules/AsyncDisplayKit/Source/Private/ASDisplayNodeLayout.h +++ b/submodules/AsyncDisplayKit/Source/ASDisplayNodeLayout.h @@ -9,6 +9,7 @@ #pragma once +#import #import @class ASLayout; diff --git a/submodules/AsyncDisplayKit/Source/ASEditableTextNode.mm b/submodules/AsyncDisplayKit/Source/ASEditableTextNode.mm index 21f94b4493..87baeb09ac 100644 --- a/submodules/AsyncDisplayKit/Source/ASEditableTextNode.mm +++ b/submodules/AsyncDisplayKit/Source/ASEditableTextNode.mm @@ -15,7 +15,7 @@ #import #import #import -#import +#import "ASTextNodeWordKerner.h" #import @implementation ASEditableTextNodeTargetForAction diff --git a/submodules/AsyncDisplayKit/Source/Details/ASGraphicsContext.mm b/submodules/AsyncDisplayKit/Source/ASGraphicsContext.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/Details/ASGraphicsContext.mm rename to submodules/AsyncDisplayKit/Source/ASGraphicsContext.mm index b950613d0d..b181ee2728 100644 --- a/submodules/AsyncDisplayKit/Source/Details/ASGraphicsContext.mm +++ b/submodules/AsyncDisplayKit/Source/ASGraphicsContext.mm @@ -7,7 +7,7 @@ // #import -#import +#import "ASCGImageBuffer.h" #import #import #import diff --git a/submodules/AsyncDisplayKit/Source/Details/ASHashing.mm b/submodules/AsyncDisplayKit/Source/ASHashing.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/Details/ASHashing.mm rename to submodules/AsyncDisplayKit/Source/ASHashing.mm diff --git a/submodules/AsyncDisplayKit/Source/ASImageNode+AnimatedImage.mm b/submodules/AsyncDisplayKit/Source/ASImageNode+AnimatedImage.mm deleted file mode 100644 index 7e4ccc2d10..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASImageNode+AnimatedImage.mm +++ /dev/null @@ -1,416 +0,0 @@ -// -// ASImageNode+AnimatedImage.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#define ASAnimatedImageDebug 0 - -#ifndef MINIMAL_ASDK -@interface ASNetworkImageNode (Private) -- (void)_locked_setDefaultImage:(UIImage *)image; -@end -#endif - - -@implementation ASImageNode (AnimatedImage) - -#pragma mark - GIF support - -- (void)setAnimatedImage:(id )animatedImage -{ - ASLockScopeSelf(); - [self _locked_setAnimatedImage:animatedImage]; -} - -- (void)_locked_setAnimatedImage:(id )animatedImage -{ - ASAssertLocked(__instanceLock__); - - if (ASObjectIsEqual(_animatedImage, animatedImage) && (animatedImage == nil || animatedImage.playbackReady)) { - return; - } - - __block id previousAnimatedImage = _animatedImage; - - _animatedImage = animatedImage; - - if (animatedImage != nil) { - __weak ASImageNode *weakSelf = self; - if ([animatedImage respondsToSelector:@selector(setCoverImageReadyCallback:)]) { - animatedImage.coverImageReadyCallback = ^(UIImage *coverImage) { - // In this case the lock is already gone we have to call the unlocked version therefore - [weakSelf setCoverImageCompleted:coverImage]; - }; - } - - animatedImage.playbackReadyCallback = ^{ - // In this case the lock is already gone we have to call the unlocked version therefore - [weakSelf setShouldAnimate:YES]; - }; - if (animatedImage.playbackReady) { - [self _locked_setShouldAnimate:YES]; - } - } else { - // Clean up after ourselves. - - // Don't bother using a `_locked` version for setting contnst as it should be pretty safe calling it with - // reaquire the lock and would add overhead to introduce this version - self.contents = nil; - [self _locked_setCoverImage:nil]; - } - - // Push calling subclass to the next runloop cycle - // We have to schedule the block on the common modes otherwise the tracking mode will not be included and it will - // not fire e.g. while scrolling down - CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopCommonModes, ^(void) { - [self animatedImageSet:animatedImage previousAnimatedImage:previousAnimatedImage]; - - // Animated image can take while to dealloc, do it off the main queue - if (previousAnimatedImage != nil) { - ASPerformBackgroundDeallocation(&previousAnimatedImage); - } - }); - // Don't need to wakeup the runloop as the current is already running - // CFRunLoopWakeUp(runLoop); // Should not be necessary -} - -- (void)animatedImageSet:(id )newAnimatedImage previousAnimatedImage:(id )previousAnimatedImage -{ - // Subclass hook should not be called with the lock held - ASAssertUnlocked(__instanceLock__); - - // Subclasses may override -} - -- (id )animatedImage -{ - ASLockScopeSelf(); - return _animatedImage; -} - -- (void)setAnimatedImagePaused:(BOOL)animatedImagePaused -{ - ASLockScopeSelf(); - - _animatedImagePaused = animatedImagePaused; - - [self _locked_setShouldAnimate:!animatedImagePaused]; -} - -- (BOOL)animatedImagePaused -{ - ASLockScopeSelf(); - return _animatedImagePaused; -} - -- (void)setCoverImageCompleted:(UIImage *)coverImage -{ - if (ASInterfaceStateIncludesDisplay(self.interfaceState)) { - ASLockScopeSelf(); - [self _locked_setCoverImageCompleted:coverImage]; - } -} - -- (void)_locked_setCoverImageCompleted:(UIImage *)coverImage -{ - ASAssertLocked(__instanceLock__); - - _displayLinkLock.lock(); - BOOL setCoverImage = (_displayLink == nil) || _displayLink.paused; - _displayLinkLock.unlock(); - - if (setCoverImage) { - [self _locked_setCoverImage:coverImage]; - } -} - -- (void)setCoverImage:(UIImage *)coverImage -{ - ASLockScopeSelf(); - [self _locked_setCoverImage:coverImage]; -} - -- (void)_locked_setCoverImage:(UIImage *)coverImage -{ - ASAssertLocked(__instanceLock__); - - //If we're a network image node, we want to set the default image so - //that it will correctly be restored if it exits the range. -#ifndef MINIMAL_ASDK - if ([self isKindOfClass:[ASNetworkImageNode class]]) { - [(ASNetworkImageNode *)self _locked_setDefaultImage:coverImage]; - } else if (_displayLink == nil || _displayLink.paused == YES) { - [self _locked_setImage:coverImage]; - } -#endif -} - -- (NSString *)animatedImageRunLoopMode -{ - AS::MutexLocker l(_displayLinkLock); - return _animatedImageRunLoopMode; -} - -- (void)setAnimatedImageRunLoopMode:(NSString *)runLoopMode -{ - AS::MutexLocker l(_displayLinkLock); - - if (runLoopMode == nil) { - runLoopMode = ASAnimatedImageDefaultRunLoopMode; - } - - if (_displayLink != nil) { - [_displayLink removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:_animatedImageRunLoopMode]; - [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:runLoopMode]; - } - _animatedImageRunLoopMode = [runLoopMode copy]; -} - -- (void)setShouldAnimate:(BOOL)shouldAnimate -{ - ASLockScopeSelf(); - [self _locked_setShouldAnimate:shouldAnimate]; -} - -- (void)_locked_setShouldAnimate:(BOOL)shouldAnimate -{ - ASAssertLocked(__instanceLock__); - - // This test is explicitly done and not ASPerformBlockOnMainThread as this would perform the block immediately - // on main if called on main thread and we have to call methods locked or unlocked based on which thread we are on - if (ASDisplayNodeThreadIsMain()) { - if (shouldAnimate) { - [self _locked_startAnimating]; - } else { - [self _locked_stopAnimating]; - } - } else { - // We have to dispatch to the main thread and call the regular methods as the lock is already gone if the - // block is called - dispatch_async(dispatch_get_main_queue(), ^{ - if (shouldAnimate) { - [self startAnimating]; - } else { - [self stopAnimating]; - } - }); - } -} - -#pragma mark - Animating - -- (void)startAnimating -{ - ASDisplayNodeAssertMainThread(); - - ASLockScopeSelf(); - [self _locked_startAnimating]; -} - -- (void)_locked_startAnimating -{ - ASAssertLocked(__instanceLock__); - - // It should be safe to call self.interfaceState in this case as it will only grab the lock of the superclass - if (!ASInterfaceStateIncludesVisible(self.interfaceState)) { - return; - } - - if (_animatedImagePaused) { - return; - } - - if (_animatedImage.playbackReady == NO) { - return; - } - -#if ASAnimatedImageDebug - NSLog(@"starting animation: %p", self); -#endif - - // Get frame interval before holding display link lock to avoid deadlock - NSUInteger frameInterval = self.animatedImage.frameInterval; - AS::MutexLocker l(_displayLinkLock); - if (_displayLink == nil) { - _playHead = 0; - _displayLink = [CADisplayLink displayLinkWithTarget:[ASWeakProxy weakProxyWithTarget:self] selector:@selector(displayLinkFired:)]; - _displayLink.frameInterval = frameInterval; - _lastSuccessfulFrameIndex = NSUIntegerMax; - - [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:_animatedImageRunLoopMode]; - } else { - _displayLink.paused = NO; - } -} - -- (void)stopAnimating -{ - ASDisplayNodeAssertMainThread(); - - ASLockScopeSelf(); - [self _locked_stopAnimating]; -} - -- (void)_locked_stopAnimating -{ - ASDisplayNodeAssertMainThread(); - ASAssertLocked(__instanceLock__); - -#if ASAnimatedImageDebug - NSLog(@"stopping animation: %p", self); -#endif - ASDisplayNodeAssertMainThread(); - AS::MutexLocker l(_displayLinkLock); - _displayLink.paused = YES; - self.lastDisplayLinkFire = 0; - - [_animatedImage clearAnimatedImageCache]; -} - -#pragma mark - ASDisplayNode - -- (void)didEnterVisibleState -{ - ASDisplayNodeAssertMainThread(); - [super didEnterVisibleState]; - - if (self.animatedImage.coverImageReady) { - [self setCoverImage:self.animatedImage.coverImage]; - } - if (self.animatedImage.playbackReady) { - [self startAnimating]; - } -} - -- (void)didExitVisibleState -{ - ASDisplayNodeAssertMainThread(); - [super didExitVisibleState]; - - [self stopAnimating]; -} - -- (void)didExitDisplayState -{ - ASDisplayNodeAssertMainThread(); -#if ASAnimatedImageDebug - NSLog(@"exiting display state: %p", self); -#endif - - // Check to see if we're an animated image before calling super in case someone - // decides they want to clear out the animatedImage itself on exiting the display - // state - BOOL isAnimatedImage = self.animatedImage != nil; - [super didExitDisplayState]; - - // Also clear out the contents we've set to be good citizens, we'll put it back in when we become visible. - if (isAnimatedImage) { - self.contents = nil; - [self setCoverImage:nil]; - } -} - -#pragma mark - Display Link Callbacks - -- (void)displayLinkFired:(CADisplayLink *)displayLink -{ - ASDisplayNodeAssertMainThread(); - - CFTimeInterval timeBetweenLastFire; - if (self.lastDisplayLinkFire == 0) { - timeBetweenLastFire = 0; - } else if (AS_AVAILABLE_IOS_TVOS(10, 10)) { - timeBetweenLastFire = displayLink.targetTimestamp - displayLink.timestamp; - } else { - timeBetweenLastFire = CACurrentMediaTime() - self.lastDisplayLinkFire; - } - self.lastDisplayLinkFire = CACurrentMediaTime(); - - _playHead += timeBetweenLastFire; - - while (_playHead > self.animatedImage.totalDuration) { - // Set playhead to zero to keep from showing different frames on different playthroughs - _playHead = 0; - _playedLoops++; - } - - if (self.animatedImage.loopCount > 0 && _playedLoops >= self.animatedImage.loopCount) { - [self stopAnimating]; - return; - } - - NSUInteger frameIndex = [self frameIndexAtPlayHeadPosition:_playHead]; - if (frameIndex == _lastSuccessfulFrameIndex) { - return; - } - CGImageRef frameImage = [self.animatedImage imageAtIndex:frameIndex]; - - if (frameImage == nil) { - //Pause the display link until we get a file ready notification - displayLink.paused = YES; - self.lastDisplayLinkFire = 0; - } else { - self.contents = (__bridge id)frameImage; - _lastSuccessfulFrameIndex = frameIndex; - [self displayDidFinish]; - } -} - -- (NSUInteger)frameIndexAtPlayHeadPosition:(CFTimeInterval)playHead -{ - ASDisplayNodeAssertMainThread(); - NSUInteger frameIndex = 0; - for (NSUInteger durationIndex = 0; durationIndex < self.animatedImage.frameCount; durationIndex++) { - playHead -= [self.animatedImage durationAtIndex:durationIndex]; - if (playHead < 0) { - return frameIndex; - } - frameIndex++; - } - - return frameIndex; -} - -@end - -#pragma mark - ASImageNode(AnimatedImageInvalidation) - -@implementation ASImageNode(AnimatedImageInvalidation) - -- (void)invalidateAnimatedImage -{ - AS::MutexLocker l(_displayLinkLock); -#if ASAnimatedImageDebug - if (_displayLink) { - NSLog(@"invalidating display link"); - } -#endif - [_displayLink invalidate]; - _displayLink = nil; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASImageNode.h b/submodules/AsyncDisplayKit/Source/ASImageNode.h deleted file mode 100644 index 902591c4f6..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASImageNode.h +++ /dev/null @@ -1,219 +0,0 @@ -// -// ASImageNode.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -#ifndef MINIMAL_ASDK -@protocol ASAnimatedImageProtocol; -#endif - -/** - * Image modification block. Use to transform an image before display. - * - * @param image The image to be displayed. - * - * @return A transformed image. - */ -typedef UIImage * _Nullable (^asimagenode_modification_block_t)(UIImage *image); - - -/** - * @abstract Draws images. - * @discussion Supports cropping, tinting, and arbitrary image modification blocks. - */ -@interface ASImageNode : ASControlNode - -/** - * @abstract The image to display. - * - * @discussion The node will efficiently display stretchable images by using - * the layer's contentsCenter property. Non-stretchable images work too, of - * course. - */ -@property (nullable) UIImage *image; - -/** - @abstract The placeholder color. - */ -@property (nullable, copy) UIColor *placeholderColor; - -/** - * @abstract Indicates whether efficient cropping of the receiver is enabled. - * - * @discussion Defaults to YES. See -setCropEnabled:recropImmediately:inBounds: for more - * information. - */ -@property (getter=isCropEnabled) BOOL cropEnabled; - -/** - * @abstract Indicates that efficient downsizing of backing store should *not* be enabled. - * - * @discussion Defaults to NO. @see ASCroppedImageBackingSizeAndDrawRectInBounds for more - * information. - */ -@property BOOL forceUpscaling; - -@property (nonatomic, assign) BOOL displayWithoutProcessing; - -/** - * @abstract Forces image to be rendered at forcedSize. - * @discussion Defaults to CGSizeZero to indicate that the forcedSize should not be used. - * Setting forcedSize to non-CGSizeZero will force the backing of the layer contents to - * be forcedSize (automatically adjusted for contentsSize). - */ -@property CGSize forcedSize; - -/** - * @abstract Enables or disables efficient cropping. - * - * @param cropEnabled YES to efficiently crop the receiver's contents such that - * contents outside of its bounds are not included; NO otherwise. - * - * @param recropImmediately If the receiver has an image, YES to redisplay the - * receiver immediately; NO otherwise. - * - * @param cropBounds The bounds into which the receiver will be cropped. Useful - * if bounds are to change in response to cropping (but have not yet done so). - * - * @discussion Efficient cropping is only performed when the receiver's view's - * contentMode is UIViewContentModeScaleAspectFill. By default, cropping is - * enabled. The crop alignment may be controlled via cropAlignmentFactor. - */ -- (void)setCropEnabled:(BOOL)cropEnabled recropImmediately:(BOOL)recropImmediately inBounds:(CGRect)cropBounds; - -/** - * @abstract A value that controls how the receiver's efficient cropping is aligned. - * - * @discussion This value defines a rectangle that is to be featured by the - * receiver. The rectangle is specified as a "unit rectangle," using - * fractions of the source image's width and height, e.g. CGRectMake(0.5, 0, - * 0.5, 1.0) will feature the full right half a photo. If the cropRect is - * empty, the content mode of the receiver will be used to determine its - * dimensions, and only the cropRect's origin will be used for positioning. The - * default value of this property is CGRectMake(0.5, 0.5, 0.0, 0.0). - */ -@property CGRect cropRect; - -/** - * @abstract An optional block which can perform drawing operations on image - * during the display phase. - * - * @discussion Can be used to add image effects (such as rounding, adding - * borders, or other pattern overlays) without extraneous display calls. - */ -@property (nullable) asimagenode_modification_block_t imageModificationBlock; - -/** - * @abstract Marks the receiver as needing display and performs a block after - * display has finished. - * - * @param displayCompletionBlock The block to be performed after display has - * finished. Its `canceled` property will be YES if display was prevented or - * canceled (via displaySuspended); NO otherwise. - * - * @discussion displayCompletionBlock will be performed on the main-thread. If - * `displaySuspended` is YES, `displayCompletionBlock` is will be - * performed immediately and `YES` will be passed for `canceled`. - */ -- (void)setNeedsDisplayWithCompletion:(nullable void (^)(BOOL canceled))displayCompletionBlock; - -#if TARGET_OS_TV -/** - * A bool to track if the current appearance of the node - * is the default focus appearance. - * Exposed here so the category methods can set it. - */ -@property BOOL isDefaultFocusAppearance; -#endif - -@end - -#if TARGET_OS_TV -@interface ASImageNode (tvOS) -@end -#endif - -#ifndef MINIMAL_ASDK -@interface ASImageNode (AnimatedImage) - -/** - * @abstract The animated image to playback - * - * @discussion Set this to an object which conforms to ASAnimatedImageProtocol - * to have the ASImageNode playback an animated image. - * @warning this method should not be overridden, it may not always be called as - * another method is used internally. If you need to know when the animatedImage - * is set, override @c animatedImageSet:previousAnimatedImage: - */ -@property (nullable) id animatedImage; - -/** - * @abstract Pause the playback of an animated image. - * - * @discussion Set to YES to pause playback of an animated image and NO to resume - * playback. - */ -@property BOOL animatedImagePaused; - -/** - * @abstract The runloop mode used to animate the image. - * - * @discussion Defaults to NSRunLoopCommonModes. Another commonly used mode is NSDefaultRunLoopMode. - * Setting NSDefaultRunLoopMode will cause animation to pause while scrolling (if the ASImageNode is - * in a scroll view), which may improve scroll performance in some use cases. - */ -@property (copy) NSString *animatedImageRunLoopMode; - -/** - * @abstract Method called when animated image has been set - * - * @discussion This method is for subclasses to override so they can know if an animated image - * has been set on the node. - */ -- (void)animatedImageSet:(nullable id )newAnimatedImage previousAnimatedImage:(nullable id )previousAnimatedImage ASDISPLAYNODE_REQUIRES_SUPER; - -@end -#endif - -@interface ASImageNode (Unavailable) - -- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock AS_UNAVAILABLE(); - -- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock AS_UNAVAILABLE(); - -@end - -/** - * @abstract Image modification block that rounds (and optionally adds a border to) an image. - * - * @param borderWidth The width of the round border to draw, or zero if no border is desired. - * @param borderColor What colour border to draw. - * - * @see - * - * @return An ASImageNode image modification block. - */ -AS_EXTERN asimagenode_modification_block_t ASImageNodeRoundBorderModificationBlock(CGFloat borderWidth, UIColor * _Nullable borderColor); - -/** - * @abstract Image modification block that applies a tint color à la UIImage configured with - * renderingMode set to UIImageRenderingModeAlwaysTemplate. - * - * @param color The color to tint the image. - * - * @see - * - * @return An ASImageNode image modification block. - */ -AS_EXTERN asimagenode_modification_block_t ASImageNodeTintColorModificationBlock(UIColor *color); - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASImageNode.mm b/submodules/AsyncDisplayKit/Source/ASImageNode.mm deleted file mode 100644 index e17597bde9..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASImageNode.mm +++ /dev/null @@ -1,784 +0,0 @@ -// -// ASImageNode.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -// TODO: It would be nice to remove this dependency; it's the only subclass using more than +FrameworkSubclasses.h -#import - -static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; - -typedef void (^ASImageNodeDrawParametersBlock)(ASWeakMapEntry *entry); - -@interface ASImageNodeDrawParameters : NSObject { -@package - UIImage *_image; - BOOL _opaque; - CGRect _bounds; - CGFloat _contentsScale; - UIColor *_backgroundColor; - UIViewContentMode _contentMode; - BOOL _cropEnabled; - BOOL _forceUpscaling; - CGSize _forcedSize; - CGRect _cropRect; - CGRect _cropDisplayBounds; - asimagenode_modification_block_t _imageModificationBlock; - ASDisplayNodeContextModifier _willDisplayNodeContentWithRenderingContext; - ASDisplayNodeContextModifier _didDisplayNodeContentWithRenderingContext; - ASImageNodeDrawParametersBlock _didDrawBlock; -} - -@end - -@implementation ASImageNodeDrawParameters - -@end - -/** - * Contains all data that is needed to generate the content bitmap. - */ -@interface ASImageNodeContentsKey : NSObject - -@property (nonatomic) UIImage *image; -@property CGSize backingSize; -@property CGRect imageDrawRect; -@property BOOL isOpaque; -@property (nonatomic, copy) UIColor *backgroundColor; -@property (nonatomic) ASDisplayNodeContextModifier willDisplayNodeContentWithRenderingContext; -@property (nonatomic) ASDisplayNodeContextModifier didDisplayNodeContentWithRenderingContext; -@property (nonatomic) asimagenode_modification_block_t imageModificationBlock; - -@end - -@implementation ASImageNodeContentsKey - -- (BOOL)isEqual:(id)object -{ - if (self == object) { - return YES; - } - - // Optimization opportunity: The `isKindOfClass` call here could be avoided by not using the NSObject `isEqual:` - // convention and instead using a custom comparison function that assumes all items are heterogeneous. - // However, profiling shows that our entire `isKindOfClass` expression is only ~1/40th of the total - // overheard of our caching, so it's likely not high-impact. - if ([object isKindOfClass:[ASImageNodeContentsKey class]]) { - ASImageNodeContentsKey *other = (ASImageNodeContentsKey *)object; - return [_image isEqual:other.image] - && CGSizeEqualToSize(_backingSize, other.backingSize) - && CGRectEqualToRect(_imageDrawRect, other.imageDrawRect) - && _isOpaque == other.isOpaque - && [_backgroundColor isEqual:other.backgroundColor] - && _willDisplayNodeContentWithRenderingContext == other.willDisplayNodeContentWithRenderingContext - && _didDisplayNodeContentWithRenderingContext == other.didDisplayNodeContentWithRenderingContext - && _imageModificationBlock == other.imageModificationBlock; - } else { - return NO; - } -} - -- (NSUInteger)hash -{ -#pragma clang diagnostic push -#pragma clang diagnostic warning "-Wpadded" - struct { - NSUInteger imageHash; - CGSize backingSize; - CGRect imageDrawRect; - NSInteger isOpaque; - NSUInteger backgroundColorHash; - void *willDisplayNodeContentWithRenderingContext; - void *didDisplayNodeContentWithRenderingContext; - void *imageModificationBlock; -#pragma clang diagnostic pop - } data = { - _image.hash, - _backingSize, - _imageDrawRect, - _isOpaque, - _backgroundColor.hash, - (void *)_willDisplayNodeContentWithRenderingContext, - (void *)_didDisplayNodeContentWithRenderingContext, - (void *)_imageModificationBlock - }; - return ASHashBytes(&data, sizeof(data)); -} - -@end - - -@implementation ASImageNode -{ -@private - UIImage *_image; - ASWeakMapEntry *_weakCacheEntry; // Holds a reference that keeps our contents in cache. - UIColor *_placeholderColor; - - void (^_displayCompletionBlock)(BOOL canceled); - - // Drawing - ASTextNode *_debugLabelNode; - - // Cropping. - BOOL _cropEnabled; // Defaults to YES. - BOOL _forceUpscaling; //Defaults to NO. - CGSize _forcedSize; //Defaults to CGSizeZero, indicating no forced size. - CGRect _cropRect; // Defaults to CGRectMake(0.5, 0.5, 0, 0) - CGRect _cropDisplayBounds; // Defaults to CGRectNull -} - -@synthesize image = _image; -@synthesize imageModificationBlock = _imageModificationBlock; - -#pragma mark - Lifecycle - -- (instancetype)init -{ - if (!(self = [super init])) - return nil; - - // TODO can this be removed? - self.contentsScale = ASScreenScale(); - self.contentMode = UIViewContentModeScaleAspectFill; - self.opaque = NO; - self.clipsToBounds = YES; - - // If no backgroundColor is set to the image node and it's a subview of UITableViewCell, UITableView is setting - // the opaque value of all subviews to YES if highlighting / selection is happening and does not set it back to the - // initial value. With setting a explicit backgroundColor we can prevent that change. - self.backgroundColor = [UIColor clearColor]; - - _cropEnabled = YES; - _forceUpscaling = NO; - _cropRect = CGRectMake(0.5, 0.5, 0, 0); - _cropDisplayBounds = CGRectNull; - _placeholderColor = ASDisplayNodeDefaultPlaceholderColor(); -#ifndef MINIMAL_ASDK - _animatedImageRunLoopMode = ASAnimatedImageDefaultRunLoopMode; -#endif - - return self; -} - -- (void)dealloc -{ - // Invalidate all components around animated images -#ifndef MINIMAL_ASDK - [self invalidateAnimatedImage]; -#endif -} - -#pragma mark - Placeholder - -- (UIImage *)placeholderImage -{ - // FIXME: Replace this implementation with reusable CALayers that have .backgroundColor set. - // This would completely eliminate the memory and performance cost of the backing store. - CGSize size = self.calculatedSize; - if ((size.width * size.height) < CGFLOAT_EPSILON) { - return nil; - } - - AS::MutexLocker l(__instanceLock__); - - ASGraphicsBeginImageContextWithOptions(size, NO, 1); - [self.placeholderColor setFill]; - UIRectFill(CGRectMake(0, 0, size.width, size.height)); - UIImage *image = ASGraphicsGetImageAndEndCurrentContext(); - - return image; -} - -#pragma mark - Layout and Sizing - -- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize -{ - const auto image = ASLockedSelf(_image); - - if (image == nil) { - return [super calculateSizeThatFits:constrainedSize]; - } - - return image.size; -} - -#pragma mark - Setter / Getter - -- (void)setImage:(UIImage *)image -{ - AS::MutexLocker l(__instanceLock__); - [self _locked_setImage:image]; -} - -- (void)_locked_setImage:(UIImage *)image -{ - ASAssertLocked(__instanceLock__); - if (ASObjectIsEqual(_image, image)) { - return; - } - - UIImage *oldImage = _image; - _image = image; - - if (image != nil) { - // We explicitly call setNeedsDisplay in this case, although we know setNeedsDisplay will be called with lock held. - // Therefore we have to be careful in methods that are involved with setNeedsDisplay to not run into a deadlock - [self setNeedsDisplay]; - - if (_displayWithoutProcessing && ASDisplayNodeThreadIsMain()) { - BOOL stretchable = !UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero); - if (stretchable) { - ASDisplayNodeSetResizableContents(self, image); - } else { - self.contents = (id)image.CGImage; - } - return; - } - } else { - self.contents = nil; - } - - // Destruction of bigger images on the main thread can be expensive - // and can take some time, so we dispatch onto a bg queue to - // actually dealloc. - CGSize oldImageSize = oldImage.size; - BOOL shouldReleaseImageOnBackgroundThread = oldImageSize.width > kMinReleaseImageOnBackgroundSize.width - || oldImageSize.height > kMinReleaseImageOnBackgroundSize.height; - if (shouldReleaseImageOnBackgroundThread) { - ASPerformBackgroundDeallocation(&oldImage); - } -} - -- (UIImage *)image -{ - return ASLockedSelf(_image); -} - -- (UIColor *)placeholderColor -{ - return ASLockedSelf(_placeholderColor); -} - -- (void)setPlaceholderColor:(UIColor *)placeholderColor -{ - ASLockScopeSelf(); - if (ASCompareAssignCopy(_placeholderColor, placeholderColor)) { - _placeholderEnabled = (placeholderColor != nil); - } -} - -#pragma mark - Drawing - -- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer -{ - ASLockScopeSelf(); - - ASImageNodeDrawParameters *drawParameters = [[ASImageNodeDrawParameters alloc] init]; - drawParameters->_image = _image; - drawParameters->_bounds = [self threadSafeBounds]; - drawParameters->_opaque = self.opaque; - drawParameters->_contentsScale = _contentsScaleForDisplay; - drawParameters->_backgroundColor = self.backgroundColor; - drawParameters->_contentMode = self.contentMode; - drawParameters->_cropEnabled = _cropEnabled; - drawParameters->_forceUpscaling = _forceUpscaling; - drawParameters->_forcedSize = _forcedSize; - drawParameters->_cropRect = _cropRect; - drawParameters->_cropDisplayBounds = _cropDisplayBounds; - drawParameters->_imageModificationBlock = _imageModificationBlock; - drawParameters->_willDisplayNodeContentWithRenderingContext = _willDisplayNodeContentWithRenderingContext; - drawParameters->_didDisplayNodeContentWithRenderingContext = _didDisplayNodeContentWithRenderingContext; - - // Hack for now to retain the weak entry that was created while this drawing happened - drawParameters->_didDrawBlock = ^(ASWeakMapEntry *entry){ - ASLockScopeSelf(); - _weakCacheEntry = entry; - }; - - return drawParameters; -} - -+ (UIImage *)displayWithParameters:(id)parameter isCancelled:(NS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelled -{ - ASImageNodeDrawParameters *drawParameter = (ASImageNodeDrawParameters *)parameter; - - UIImage *image = drawParameter->_image; - if (image == nil) { - return nil; - } - - if (true) { - return image; - } - - CGRect drawParameterBounds = drawParameter->_bounds; - BOOL forceUpscaling = drawParameter->_forceUpscaling; - CGSize forcedSize = drawParameter->_forcedSize; - BOOL cropEnabled = drawParameter->_cropEnabled; - BOOL isOpaque = drawParameter->_opaque; - UIColor *backgroundColor = drawParameter->_backgroundColor; - UIViewContentMode contentMode = drawParameter->_contentMode; - CGFloat contentsScale = drawParameter->_contentsScale; - CGRect cropDisplayBounds = drawParameter->_cropDisplayBounds; - CGRect cropRect = drawParameter->_cropRect; - asimagenode_modification_block_t imageModificationBlock = drawParameter->_imageModificationBlock; - ASDisplayNodeContextModifier willDisplayNodeContentWithRenderingContext = drawParameter->_willDisplayNodeContentWithRenderingContext; - ASDisplayNodeContextModifier didDisplayNodeContentWithRenderingContext = drawParameter->_didDisplayNodeContentWithRenderingContext; - - BOOL hasValidCropBounds = cropEnabled && !CGRectIsEmpty(cropDisplayBounds); - CGRect bounds = (hasValidCropBounds ? cropDisplayBounds : drawParameterBounds); - - - ASDisplayNodeAssert(contentsScale > 0, @"invalid contentsScale at display time"); - - // if the image is resizable, bail early since the image has likely already been configured - BOOL stretchable = !UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero); - if (stretchable) { - if (imageModificationBlock != NULL) { - image = imageModificationBlock(image); - } - return image; - } - - CGSize imageSize = image.size; - CGSize imageSizeInPixels = CGSizeMake(imageSize.width * image.scale, imageSize.height * image.scale); - CGSize boundsSizeInPixels = CGSizeMake(std::floor(bounds.size.width * contentsScale), std::floor(bounds.size.height * contentsScale)); - - BOOL contentModeSupported = contentMode == UIViewContentModeScaleAspectFill || - contentMode == UIViewContentModeScaleAspectFit || - contentMode == UIViewContentModeCenter; - - CGSize backingSize = CGSizeZero; - CGRect imageDrawRect = CGRectZero; - - if (boundsSizeInPixels.width * contentsScale < 1.0f || boundsSizeInPixels.height * contentsScale < 1.0f || - imageSizeInPixels.width < 1.0f || imageSizeInPixels.height < 1.0f) { - return nil; - } - - - // If we're not supposed to do any cropping, just decode image at original size - if (!cropEnabled || !contentModeSupported || stretchable) { - backingSize = imageSizeInPixels; - imageDrawRect = (CGRect){.size = backingSize}; - } else { - if (CGSizeEqualToSize(CGSizeZero, forcedSize) == NO) { - //scale forced size - forcedSize.width *= contentsScale; - forcedSize.height *= contentsScale; - } - ASCroppedImageBackingSizeAndDrawRectInBounds(imageSizeInPixels, - boundsSizeInPixels, - contentMode, - cropRect, - forceUpscaling, - forcedSize, - &backingSize, - &imageDrawRect); - } - - if (backingSize.width <= 0.0f || backingSize.height <= 0.0f || - imageDrawRect.size.width <= 0.0f || imageDrawRect.size.height <= 0.0f) { - return nil; - } - - ASImageNodeContentsKey *contentsKey = [[ASImageNodeContentsKey alloc] init]; - contentsKey.image = image; - contentsKey.backingSize = backingSize; - contentsKey.imageDrawRect = imageDrawRect; - contentsKey.isOpaque = isOpaque; - contentsKey.backgroundColor = backgroundColor; - contentsKey.willDisplayNodeContentWithRenderingContext = willDisplayNodeContentWithRenderingContext; - contentsKey.didDisplayNodeContentWithRenderingContext = didDisplayNodeContentWithRenderingContext; - contentsKey.imageModificationBlock = imageModificationBlock; - - if (isCancelled()) { - return nil; - } - - ASWeakMapEntry *entry = [self.class contentsForkey:contentsKey - drawParameters:parameter - isCancelled:isCancelled]; - // If nil, we were cancelled. - if (entry == nil) { - return nil; - } - - if (drawParameter->_didDrawBlock) { - drawParameter->_didDrawBlock(entry); - } - - return entry.value; -} - -static ASWeakMap *cache = nil; - -+ (ASWeakMapEntry *)contentsForkey:(ASImageNodeContentsKey *)key drawParameters:(id)drawParameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled -{ - static dispatch_once_t onceToken; - static AS::Mutex *cacheLock = nil; - dispatch_once(&onceToken, ^{ - cacheLock = new AS::Mutex(); - }); - - { - AS::MutexLocker l(*cacheLock); - if (!cache) { - cache = [[ASWeakMap alloc] init]; - } - ASWeakMapEntry *entry = [cache entryForKey:key]; - if (entry != nil) { - return entry; - } - } - - // cache miss - UIImage *contents = [self createContentsForkey:key drawParameters:drawParameters isCancelled:isCancelled]; - if (contents == nil) { // If nil, we were cancelled - return nil; - } - - { - AS::MutexLocker l(*cacheLock); - return [cache setObject:contents forKey:key]; - } -} - -+ (UIImage *)createContentsForkey:(ASImageNodeContentsKey *)key drawParameters:(id)drawParameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled -{ - // The following `ASGraphicsBeginImageContextWithOptions` call will sometimes take take longer than 5ms on an - // A5 processor for a 400x800 backingSize. - // Check for cancellation before we call it. - if (isCancelled()) { - return nil; - } - - // Use contentsScale of 1.0 and do the contentsScale handling in boundsSizeInPixels so ASCroppedImageBackingSizeAndDrawRectInBounds - // will do its rounding on pixel instead of point boundaries - ASGraphicsBeginImageContextWithOptions(key.backingSize, key.isOpaque, 1.0); - - BOOL contextIsClean = YES; - - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context && key.willDisplayNodeContentWithRenderingContext) { - key.willDisplayNodeContentWithRenderingContext(context, drawParameters); - contextIsClean = NO; - } - - // if view is opaque, fill the context with background color - if (key.isOpaque && key.backgroundColor) { - [key.backgroundColor setFill]; - UIRectFill({ .size = key.backingSize }); - contextIsClean = NO; - } - - // iOS 9 appears to contain a thread safety regression when drawing the same CGImageRef on - // multiple threads concurrently. In fact, instead of crashing, it appears to deadlock. - // The issue is present in Mac OS X El Capitan and has been seen hanging Pro apps like Adobe Premiere, - // as well as iOS games, and a small number of ASDK apps that provide the same image reference - // to many separate ASImageNodes. A workaround is to set .displaysAsynchronously = NO for the nodes - // that may get the same pointer for a given UI asset image, etc. - // FIXME: We should replace @synchronized here, probably using a global, locked NSMutableSet, and - // only if the object already exists in the set we should create a semaphore to signal waiting threads - // upon removal of the object from the set when the operation completes. - // Another option is to have ASDisplayNode+AsyncDisplay coordinate these cases, and share the decoded buffer. - // Details tracked in https://github.com/facebook/AsyncDisplayKit/issues/1068 - - UIImage *image = key.image; - BOOL canUseCopy = (contextIsClean || ASImageAlphaInfoIsOpaque(CGImageGetAlphaInfo(image.CGImage))); - CGBlendMode blendMode = canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal; - - @synchronized(image) { - [image drawInRect:key.imageDrawRect blendMode:blendMode alpha:1]; - } - - if (context && key.didDisplayNodeContentWithRenderingContext) { - key.didDisplayNodeContentWithRenderingContext(context, drawParameters); - } - - // Check cancellation one last time before forming image. - if (isCancelled()) { - ASGraphicsEndImageContext(); - return nil; - } - - UIImage *result = ASGraphicsGetImageAndEndCurrentContext(); - - if (key.imageModificationBlock) { - result = key.imageModificationBlock(result); - } - - return result; -} - -- (void)displayDidFinish -{ - [super displayDidFinish]; - - __instanceLock__.lock(); - UIImage *image = _image; - void (^displayCompletionBlock)(BOOL canceled) = _displayCompletionBlock; - BOOL shouldPerformDisplayCompletionBlock = (image && displayCompletionBlock); - - // Clear the ivar now. The block is retained and will be executed shortly. - if (shouldPerformDisplayCompletionBlock) { - _displayCompletionBlock = nil; - } - - BOOL hasDebugLabel = (_debugLabelNode != nil); - __instanceLock__.unlock(); - - // Update the debug label if necessary - if (hasDebugLabel) { - // For debugging purposes we don't care about locking for now - CGSize imageSize = image.size; - CGSize imageSizeInPixels = CGSizeMake(imageSize.width * image.scale, imageSize.height * image.scale); - CGSize boundsSizeInPixels = CGSizeMake(std::floor(self.bounds.size.width * self.contentsScale), std::floor(self.bounds.size.height * self.contentsScale)); - CGFloat pixelCountRatio = (imageSizeInPixels.width * imageSizeInPixels.height) / (boundsSizeInPixels.width * boundsSizeInPixels.height); - if (pixelCountRatio != 1.0) { - NSString *scaleString = [NSString stringWithFormat:@"%.2fx", pixelCountRatio]; - _debugLabelNode.attributedText = [[NSAttributedString alloc] initWithString:scaleString attributes:[self debugLabelAttributes]]; - _debugLabelNode.hidden = NO; - } else { - _debugLabelNode.hidden = YES; - _debugLabelNode.attributedText = nil; - } - } - - // If we've got a block to perform after displaying, do it. - if (shouldPerformDisplayCompletionBlock) { - displayCompletionBlock(NO); - } -} - -- (void)setNeedsDisplayWithCompletion:(void (^ _Nullable)(BOOL canceled))displayCompletionBlock -{ - if (self.displaySuspended) { - if (displayCompletionBlock) - displayCompletionBlock(YES); - return; - } - - // Stash the block and call-site queue. We'll invoke it in -displayDidFinish. - { - AS::MutexLocker l(__instanceLock__); - if (_displayCompletionBlock != displayCompletionBlock) { - _displayCompletionBlock = displayCompletionBlock; - } - } - - [self setNeedsDisplay]; -} - -#pragma mark Interface State - -- (void)clearContents -{ - [super clearContents]; - - AS::MutexLocker l(__instanceLock__); - _weakCacheEntry = nil; // release contents from the cache. -} - -#pragma mark - Cropping - -- (BOOL)isCropEnabled -{ - AS::MutexLocker l(__instanceLock__); - return _cropEnabled; -} - -- (void)setCropEnabled:(BOOL)cropEnabled -{ - [self setCropEnabled:cropEnabled recropImmediately:NO inBounds:self.bounds]; -} - -- (void)setCropEnabled:(BOOL)cropEnabled recropImmediately:(BOOL)recropImmediately inBounds:(CGRect)cropBounds -{ - __instanceLock__.lock(); - if (_cropEnabled == cropEnabled) { - __instanceLock__.unlock(); - return; - } - - _cropEnabled = cropEnabled; - _cropDisplayBounds = cropBounds; - - UIImage *image = _image; - __instanceLock__.unlock(); - - // If we have an image to display, display it, respecting our recrop flag. - if (image != nil) { - ASPerformBlockOnMainThread(^{ - if (recropImmediately) - [self displayImmediately]; - else - [self setNeedsDisplay]; - }); - } -} - -- (CGRect)cropRect -{ - AS::MutexLocker l(__instanceLock__); - return _cropRect; -} - -- (void)setCropRect:(CGRect)cropRect -{ - { - AS::MutexLocker l(__instanceLock__); - if (CGRectEqualToRect(_cropRect, cropRect)) { - return; - } - - _cropRect = cropRect; - } - - // TODO: this logic needs to be updated to respect cropRect. - CGSize boundsSize = self.bounds.size; - CGSize imageSize = self.image.size; - - BOOL isCroppingImage = ((boundsSize.width < imageSize.width) || (boundsSize.height < imageSize.height)); - - // Re-display if we need to. - ASPerformBlockOnMainThread(^{ - if (self.nodeLoaded && self.contentMode == UIViewContentModeScaleAspectFill && isCroppingImage) - [self setNeedsDisplay]; - }); -} - -- (BOOL)forceUpscaling -{ - AS::MutexLocker l(__instanceLock__); - return _forceUpscaling; -} - -- (void)setForceUpscaling:(BOOL)forceUpscaling -{ - AS::MutexLocker l(__instanceLock__); - _forceUpscaling = forceUpscaling; -} - -- (CGSize)forcedSize -{ - AS::MutexLocker l(__instanceLock__); - return _forcedSize; -} - -- (void)setForcedSize:(CGSize)forcedSize -{ - AS::MutexLocker l(__instanceLock__); - _forcedSize = forcedSize; -} - -- (asimagenode_modification_block_t)imageModificationBlock -{ - AS::MutexLocker l(__instanceLock__); - return _imageModificationBlock; -} - -- (void)setImageModificationBlock:(asimagenode_modification_block_t)imageModificationBlock -{ - AS::MutexLocker l(__instanceLock__); - _imageModificationBlock = imageModificationBlock; -} - -#pragma mark - Debug - -- (void)layout -{ - [super layout]; - - if (_debugLabelNode) { - CGSize boundsSize = self.bounds.size; - CGSize debugLabelSize = [_debugLabelNode layoutThatFits:ASSizeRangeMake(CGSizeZero, boundsSize)].size; - CGPoint debugLabelOrigin = CGPointMake(boundsSize.width - debugLabelSize.width, - boundsSize.height - debugLabelSize.height); - _debugLabelNode.frame = (CGRect) {debugLabelOrigin, debugLabelSize}; - } -} - -- (NSDictionary *)debugLabelAttributes -{ - return @{ - NSFontAttributeName: [UIFont systemFontOfSize:15.0], - NSForegroundColorAttributeName: [UIColor redColor] - }; -} - -@end - -#pragma mark - Extras - -asimagenode_modification_block_t ASImageNodeRoundBorderModificationBlock(CGFloat borderWidth, UIColor *borderColor) -{ - return ^(UIImage *originalImage) { - ASGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale); - UIBezierPath *roundOutline = [UIBezierPath bezierPathWithOvalInRect:(CGRect){CGPointZero, originalImage.size}]; - - // Make the image round - [roundOutline addClip]; - - // Draw the original image - [originalImage drawAtPoint:CGPointZero blendMode:kCGBlendModeCopy alpha:1]; - - // Draw a border on top. - if (borderWidth > 0.0) { - [borderColor setStroke]; - [roundOutline setLineWidth:borderWidth]; - [roundOutline stroke]; - } - - return ASGraphicsGetImageAndEndCurrentContext(); - }; -} - -asimagenode_modification_block_t ASImageNodeTintColorModificationBlock(UIColor *color) -{ - return ^(UIImage *originalImage) { - ASGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale); - - // Set color and render template - [color setFill]; - UIImage *templateImage = [originalImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; - [templateImage drawAtPoint:CGPointZero blendMode:kCGBlendModeCopy alpha:1]; - - UIImage *modifiedImage = ASGraphicsGetImageAndEndCurrentContext(); - - // if the original image was stretchy, keep it stretchy - if (!UIEdgeInsetsEqualToEdgeInsets(originalImage.capInsets, UIEdgeInsetsZero)) { - modifiedImage = [modifiedImage resizableImageWithCapInsets:originalImage.capInsets resizingMode:originalImage.resizingMode]; - } - - return modifiedImage; - }; -} diff --git a/submodules/AsyncDisplayKit/Source/ASImageNode.mm.orig b/submodules/AsyncDisplayKit/Source/ASImageNode.mm.orig deleted file mode 100644 index 0106295a55..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASImageNode.mm.orig +++ /dev/null @@ -1,791 +0,0 @@ -// -// ASImageNode.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -// TODO: It would be nice to remove this dependency; it's the only subclass using more than +FrameworkSubclasses.h -#import - -static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; - -typedef void (^ASImageNodeDrawParametersBlock)(ASWeakMapEntry *entry); - -@interface ASImageNodeDrawParameters : NSObject { -@package - UIImage *_image; - BOOL _opaque; - CGRect _bounds; - CGFloat _contentsScale; - UIColor *_backgroundColor; - UIViewContentMode _contentMode; - BOOL _cropEnabled; - BOOL _forceUpscaling; - CGSize _forcedSize; - CGRect _cropRect; - CGRect _cropDisplayBounds; - asimagenode_modification_block_t _imageModificationBlock; - ASDisplayNodeContextModifier _willDisplayNodeContentWithRenderingContext; - ASDisplayNodeContextModifier _didDisplayNodeContentWithRenderingContext; - ASImageNodeDrawParametersBlock _didDrawBlock; -} - -@end - -@implementation ASImageNodeDrawParameters - -@end - -/** - * Contains all data that is needed to generate the content bitmap. - */ -@interface ASImageNodeContentsKey : NSObject - -@property (nonatomic) UIImage *image; -@property CGSize backingSize; -@property CGRect imageDrawRect; -@property BOOL isOpaque; -@property (nonatomic, copy) UIColor *backgroundColor; -@property (nonatomic) ASDisplayNodeContextModifier willDisplayNodeContentWithRenderingContext; -@property (nonatomic) ASDisplayNodeContextModifier didDisplayNodeContentWithRenderingContext; -@property (nonatomic) asimagenode_modification_block_t imageModificationBlock; - -@end - -@implementation ASImageNodeContentsKey - -- (BOOL)isEqual:(id)object -{ - if (self == object) { - return YES; - } - - // Optimization opportunity: The `isKindOfClass` call here could be avoided by not using the NSObject `isEqual:` - // convention and instead using a custom comparison function that assumes all items are heterogeneous. - // However, profiling shows that our entire `isKindOfClass` expression is only ~1/40th of the total - // overheard of our caching, so it's likely not high-impact. - if ([object isKindOfClass:[ASImageNodeContentsKey class]]) { - ASImageNodeContentsKey *other = (ASImageNodeContentsKey *)object; - return [_image isEqual:other.image] - && CGSizeEqualToSize(_backingSize, other.backingSize) - && CGRectEqualToRect(_imageDrawRect, other.imageDrawRect) - && _isOpaque == other.isOpaque - && [_backgroundColor isEqual:other.backgroundColor] - && _willDisplayNodeContentWithRenderingContext == other.willDisplayNodeContentWithRenderingContext - && _didDisplayNodeContentWithRenderingContext == other.didDisplayNodeContentWithRenderingContext - && _imageModificationBlock == other.imageModificationBlock; - } else { - return NO; - } -} - -- (NSUInteger)hash -{ -#pragma clang diagnostic push -#pragma clang diagnostic warning "-Wpadded" - struct { - NSUInteger imageHash; - CGSize backingSize; - CGRect imageDrawRect; - NSInteger isOpaque; - NSUInteger backgroundColorHash; - void *willDisplayNodeContentWithRenderingContext; - void *didDisplayNodeContentWithRenderingContext; - void *imageModificationBlock; -#pragma clang diagnostic pop - } data = { - _image.hash, - _backingSize, - _imageDrawRect, - _isOpaque, - _backgroundColor.hash, - (void *)_willDisplayNodeContentWithRenderingContext, - (void *)_didDisplayNodeContentWithRenderingContext, - (void *)_imageModificationBlock - }; - return ASHashBytes(&data, sizeof(data)); -} - -@end - - -@implementation ASImageNode -{ -@private - UIImage *_image; - ASWeakMapEntry *_weakCacheEntry; // Holds a reference that keeps our contents in cache. - UIColor *_placeholderColor; - - void (^_displayCompletionBlock)(BOOL canceled); - - // Drawing - ASTextNode *_debugLabelNode; - - // Cropping. - BOOL _cropEnabled; // Defaults to YES. - BOOL _forceUpscaling; //Defaults to NO. - CGSize _forcedSize; //Defaults to CGSizeZero, indicating no forced size. - CGRect _cropRect; // Defaults to CGRectMake(0.5, 0.5, 0, 0) - CGRect _cropDisplayBounds; // Defaults to CGRectNull -} - -@synthesize image = _image; -@synthesize imageModificationBlock = _imageModificationBlock; - -#pragma mark - Lifecycle - -- (instancetype)init -{ - if (!(self = [super init])) - return nil; - - // TODO can this be removed? - self.contentsScale = ASScreenScale(); - self.contentMode = UIViewContentModeScaleAspectFill; - self.opaque = NO; - self.clipsToBounds = YES; - - // If no backgroundColor is set to the image node and it's a subview of UITableViewCell, UITableView is setting - // the opaque value of all subviews to YES if highlighting / selection is happening and does not set it back to the - // initial value. With setting a explicit backgroundColor we can prevent that change. - self.backgroundColor = [UIColor clearColor]; - - _cropEnabled = YES; - _forceUpscaling = NO; - _cropRect = CGRectMake(0.5, 0.5, 0, 0); - _cropDisplayBounds = CGRectNull; - _placeholderColor = ASDisplayNodeDefaultPlaceholderColor(); -#ifndef MINIMAL_ASDK - _animatedImageRunLoopMode = ASAnimatedImageDefaultRunLoopMode; -#endif - - return self; -} - -- (void)dealloc -{ - // Invalidate all components around animated images -#ifndef MINIMAL_ASDK - [self invalidateAnimatedImage]; -#endif -} - -#pragma mark - Placeholder - -- (UIImage *)placeholderImage -{ - // FIXME: Replace this implementation with reusable CALayers that have .backgroundColor set. - // This would completely eliminate the memory and performance cost of the backing store. - CGSize size = self.calculatedSize; - if ((size.width * size.height) < CGFLOAT_EPSILON) { - return nil; - } - - ASDN::MutexLocker l(__instanceLock__); - - ASGraphicsBeginImageContextWithOptions(size, NO, 1); - [self.placeholderColor setFill]; - UIRectFill(CGRectMake(0, 0, size.width, size.height)); - UIImage *image = ASGraphicsGetImageAndEndCurrentContext(); - - return image; -} - -#pragma mark - Layout and Sizing - -- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize -{ - let image = ASLockedSelf(_image); - - if (image == nil) { - return [super calculateSizeThatFits:constrainedSize]; - } - - return image.size; -} - -#pragma mark - Setter / Getter - -- (void)setImage:(UIImage *)image -{ - ASDN::MutexLocker l(__instanceLock__); - [self _locked_setImage:image]; -} - -- (void)_locked_setImage:(UIImage *)image -{ - ASAssertLocked(__instanceLock__); - if (ASObjectIsEqual(_image, image)) { - return; - } - - UIImage *oldImage = _image; - _image = image; - - if (image != nil) { - // We explicitly call setNeedsDisplay in this case, although we know setNeedsDisplay will be called with lock held. - // Therefore we have to be careful in methods that are involved with setNeedsDisplay to not run into a deadlock - [self setNeedsDisplay]; - -<<<<<<< HEAD - if (_displayWithoutProcessing && ASDisplayNodeThreadIsMain()) { - BOOL stretchable = !UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero); - if (stretchable) { - ASDisplayNodeSetupLayerContentsWithResizableImage(self.layer, image); - } else { - self.contents = (id)image.CGImage; - } - return; -======= - // For debugging purposes we don't care about locking for now - if ([ASImageNode shouldShowImageScalingOverlay] && _debugLabelNode == nil) { - // do not use ASPerformBlockOnMainThread here, if it performs the block synchronously it will continue - // holding the lock while calling addSubnode. - dispatch_async(dispatch_get_main_queue(), ^{ - _debugLabelNode = [[ASTextNode alloc] init]; - _debugLabelNode.layerBacked = YES; - [self addSubnode:_debugLabelNode]; - }); ->>>>>>> 565da7d4935740d12fc204aa061faf093831da1e - } - } else { - self.contents = nil; - } - - // Destruction of bigger images on the main thread can be expensive - // and can take some time, so we dispatch onto a bg queue to - // actually dealloc. - CGSize oldImageSize = oldImage.size; - BOOL shouldReleaseImageOnBackgroundThread = oldImageSize.width > kMinReleaseImageOnBackgroundSize.width - || oldImageSize.height > kMinReleaseImageOnBackgroundSize.height; - if (shouldReleaseImageOnBackgroundThread) { - ASPerformBackgroundDeallocation(&oldImage); - } -} - -- (UIImage *)image -{ - return ASLockedSelf(_image); -} - -- (UIColor *)placeholderColor -{ - return ASLockedSelf(_placeholderColor); -} - -- (void)setPlaceholderColor:(UIColor *)placeholderColor -{ - ASLockScopeSelf(); - if (ASCompareAssignCopy(_placeholderColor, placeholderColor)) { - _placeholderEnabled = (placeholderColor != nil); - } -} - -#pragma mark - Drawing - -- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer -{ - ASLockScopeSelf(); - - ASImageNodeDrawParameters *drawParameters = [[ASImageNodeDrawParameters alloc] init]; - drawParameters->_image = _image; - drawParameters->_bounds = [self threadSafeBounds]; - drawParameters->_opaque = self.opaque; - drawParameters->_contentsScale = _contentsScaleForDisplay; - drawParameters->_backgroundColor = self.backgroundColor; - drawParameters->_contentMode = self.contentMode; - drawParameters->_cropEnabled = _cropEnabled; - drawParameters->_forceUpscaling = _forceUpscaling; - drawParameters->_forcedSize = _forcedSize; - drawParameters->_cropRect = _cropRect; - drawParameters->_cropDisplayBounds = _cropDisplayBounds; - drawParameters->_imageModificationBlock = _imageModificationBlock; - drawParameters->_willDisplayNodeContentWithRenderingContext = _willDisplayNodeContentWithRenderingContext; - drawParameters->_didDisplayNodeContentWithRenderingContext = _didDisplayNodeContentWithRenderingContext; - - // Hack for now to retain the weak entry that was created while this drawing happened - drawParameters->_didDrawBlock = ^(ASWeakMapEntry *entry){ - ASLockScopeSelf(); - _weakCacheEntry = entry; - }; - - return drawParameters; -} - -+ (UIImage *)displayWithParameters:(id)parameter isCancelled:(NS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelled -{ - ASImageNodeDrawParameters *drawParameter = (ASImageNodeDrawParameters *)parameter; - - UIImage *image = drawParameter->_image; - if (image == nil) { - return nil; - } - - if (true) { - return image; - } - - CGRect drawParameterBounds = drawParameter->_bounds; - BOOL forceUpscaling = drawParameter->_forceUpscaling; - CGSize forcedSize = drawParameter->_forcedSize; - BOOL cropEnabled = drawParameter->_cropEnabled; - BOOL isOpaque = drawParameter->_opaque; - UIColor *backgroundColor = drawParameter->_backgroundColor; - UIViewContentMode contentMode = drawParameter->_contentMode; - CGFloat contentsScale = drawParameter->_contentsScale; - CGRect cropDisplayBounds = drawParameter->_cropDisplayBounds; - CGRect cropRect = drawParameter->_cropRect; - asimagenode_modification_block_t imageModificationBlock = drawParameter->_imageModificationBlock; - ASDisplayNodeContextModifier willDisplayNodeContentWithRenderingContext = drawParameter->_willDisplayNodeContentWithRenderingContext; - ASDisplayNodeContextModifier didDisplayNodeContentWithRenderingContext = drawParameter->_didDisplayNodeContentWithRenderingContext; - - BOOL hasValidCropBounds = cropEnabled && !CGRectIsEmpty(cropDisplayBounds); - CGRect bounds = (hasValidCropBounds ? cropDisplayBounds : drawParameterBounds); - - - ASDisplayNodeAssert(contentsScale > 0, @"invalid contentsScale at display time"); - - // if the image is resizable, bail early since the image has likely already been configured - BOOL stretchable = !UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero); - if (stretchable) { - if (imageModificationBlock != NULL) { - image = imageModificationBlock(image); - } - return image; - } - - CGSize imageSize = image.size; - CGSize imageSizeInPixels = CGSizeMake(imageSize.width * image.scale, imageSize.height * image.scale); - CGSize boundsSizeInPixels = CGSizeMake(std::floor(bounds.size.width * contentsScale), std::floor(bounds.size.height * contentsScale)); - - BOOL contentModeSupported = contentMode == UIViewContentModeScaleAspectFill || - contentMode == UIViewContentModeScaleAspectFit || - contentMode == UIViewContentModeCenter; - - CGSize backingSize = CGSizeZero; - CGRect imageDrawRect = CGRectZero; - - if (boundsSizeInPixels.width * contentsScale < 1.0f || boundsSizeInPixels.height * contentsScale < 1.0f || - imageSizeInPixels.width < 1.0f || imageSizeInPixels.height < 1.0f) { - return nil; - } - - - // If we're not supposed to do any cropping, just decode image at original size - if (!cropEnabled || !contentModeSupported || stretchable) { - backingSize = imageSizeInPixels; - imageDrawRect = (CGRect){.size = backingSize}; - } else { - if (CGSizeEqualToSize(CGSizeZero, forcedSize) == NO) { - //scale forced size - forcedSize.width *= contentsScale; - forcedSize.height *= contentsScale; - } - ASCroppedImageBackingSizeAndDrawRectInBounds(imageSizeInPixels, - boundsSizeInPixels, - contentMode, - cropRect, - forceUpscaling, - forcedSize, - &backingSize, - &imageDrawRect); - } - - if (backingSize.width <= 0.0f || backingSize.height <= 0.0f || - imageDrawRect.size.width <= 0.0f || imageDrawRect.size.height <= 0.0f) { - return nil; - } - - ASImageNodeContentsKey *contentsKey = [[ASImageNodeContentsKey alloc] init]; - contentsKey.image = image; - contentsKey.backingSize = backingSize; - contentsKey.imageDrawRect = imageDrawRect; - contentsKey.isOpaque = isOpaque; - contentsKey.backgroundColor = backgroundColor; - contentsKey.willDisplayNodeContentWithRenderingContext = willDisplayNodeContentWithRenderingContext; - contentsKey.didDisplayNodeContentWithRenderingContext = didDisplayNodeContentWithRenderingContext; - contentsKey.imageModificationBlock = imageModificationBlock; - - if (isCancelled()) { - return nil; - } - - ASWeakMapEntry *entry = [self.class contentsForkey:contentsKey - drawParameters:parameter - isCancelled:isCancelled]; - // If nil, we were cancelled. - if (entry == nil) { - return nil; - } - - if (drawParameter->_didDrawBlock) { - drawParameter->_didDrawBlock(entry); - } - - return entry.value; -} - -static ASWeakMap *cache = nil; -// Allocate cacheLock on the heap to prevent destruction at app exit (https://github.com/TextureGroup/Texture/issues/136) -static ASDN::StaticMutex& cacheLock = *new ASDN::StaticMutex; - -+ (ASWeakMapEntry *)contentsForkey:(ASImageNodeContentsKey *)key drawParameters:(id)drawParameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled -{ - { - ASDN::StaticMutexLocker l(cacheLock); - if (!cache) { - cache = [[ASWeakMap alloc] init]; - } - ASWeakMapEntry *entry = [cache entryForKey:key]; - if (entry != nil) { - return entry; - } - } - - // cache miss - UIImage *contents = [self createContentsForkey:key drawParameters:drawParameters isCancelled:isCancelled]; - if (contents == nil) { // If nil, we were cancelled - return nil; - } - - { - ASDN::StaticMutexLocker l(cacheLock); - return [cache setObject:contents forKey:key]; - } -} - -+ (UIImage *)createContentsForkey:(ASImageNodeContentsKey *)key drawParameters:(id)drawParameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled -{ - // The following `ASGraphicsBeginImageContextWithOptions` call will sometimes take take longer than 5ms on an - // A5 processor for a 400x800 backingSize. - // Check for cancellation before we call it. - if (isCancelled()) { - return nil; - } - - // Use contentsScale of 1.0 and do the contentsScale handling in boundsSizeInPixels so ASCroppedImageBackingSizeAndDrawRectInBounds - // will do its rounding on pixel instead of point boundaries - ASGraphicsBeginImageContextWithOptions(key.backingSize, key.isOpaque, 1.0); - - BOOL contextIsClean = YES; - - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context && key.willDisplayNodeContentWithRenderingContext) { - key.willDisplayNodeContentWithRenderingContext(context, drawParameters); - contextIsClean = NO; - } - - // if view is opaque, fill the context with background color - if (key.isOpaque && key.backgroundColor) { - [key.backgroundColor setFill]; - UIRectFill({ .size = key.backingSize }); - contextIsClean = NO; - } - - // iOS 9 appears to contain a thread safety regression when drawing the same CGImageRef on - // multiple threads concurrently. In fact, instead of crashing, it appears to deadlock. - // The issue is present in Mac OS X El Capitan and has been seen hanging Pro apps like Adobe Premiere, - // as well as iOS games, and a small number of ASDK apps that provide the same image reference - // to many separate ASImageNodes. A workaround is to set .displaysAsynchronously = NO for the nodes - // that may get the same pointer for a given UI asset image, etc. - // FIXME: We should replace @synchronized here, probably using a global, locked NSMutableSet, and - // only if the object already exists in the set we should create a semaphore to signal waiting threads - // upon removal of the object from the set when the operation completes. - // Another option is to have ASDisplayNode+AsyncDisplay coordinate these cases, and share the decoded buffer. - // Details tracked in https://github.com/facebook/AsyncDisplayKit/issues/1068 - - UIImage *image = key.image; - BOOL canUseCopy = (contextIsClean || ASImageAlphaInfoIsOpaque(CGImageGetAlphaInfo(image.CGImage))); - CGBlendMode blendMode = canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal; - - @synchronized(image) { - [image drawInRect:key.imageDrawRect blendMode:blendMode alpha:1]; - } - - if (context && key.didDisplayNodeContentWithRenderingContext) { - key.didDisplayNodeContentWithRenderingContext(context, drawParameters); - } - - // Check cancellation one last time before forming image. - if (isCancelled()) { - ASGraphicsEndImageContext(); - return nil; - } - - UIImage *result = ASGraphicsGetImageAndEndCurrentContext(); - - if (key.imageModificationBlock) { - result = key.imageModificationBlock(result); - } - - return result; -} - -- (void)displayDidFinish -{ - [super displayDidFinish]; - - __instanceLock__.lock(); - void (^displayCompletionBlock)(BOOL canceled) = _displayCompletionBlock; - UIImage *image = _image; - BOOL hasDebugLabel = (_debugLabelNode != nil); - __instanceLock__.unlock(); - - // Update the debug label if necessary - if (hasDebugLabel) { - // For debugging purposes we don't care about locking for now - CGSize imageSize = image.size; - CGSize imageSizeInPixels = CGSizeMake(imageSize.width * image.scale, imageSize.height * image.scale); - CGSize boundsSizeInPixels = CGSizeMake(std::floor(self.bounds.size.width * self.contentsScale), std::floor(self.bounds.size.height * self.contentsScale)); - CGFloat pixelCountRatio = (imageSizeInPixels.width * imageSizeInPixels.height) / (boundsSizeInPixels.width * boundsSizeInPixels.height); - if (pixelCountRatio != 1.0) { - NSString *scaleString = [NSString stringWithFormat:@"%.2fx", pixelCountRatio]; - _debugLabelNode.attributedText = [[NSAttributedString alloc] initWithString:scaleString attributes:[self debugLabelAttributes]]; - _debugLabelNode.hidden = NO; - } else { - _debugLabelNode.hidden = YES; - _debugLabelNode.attributedText = nil; - } - } - - // If we've got a block to perform after displaying, do it. - if (image && displayCompletionBlock) { - - displayCompletionBlock(NO); - - __instanceLock__.lock(); - _displayCompletionBlock = nil; - __instanceLock__.unlock(); - } -} - -- (void)setNeedsDisplayWithCompletion:(void (^ _Nullable)(BOOL canceled))displayCompletionBlock -{ - if (self.displaySuspended) { - if (displayCompletionBlock) - displayCompletionBlock(YES); - return; - } - - // Stash the block and call-site queue. We'll invoke it in -displayDidFinish. - { - ASDN::MutexLocker l(__instanceLock__); - if (_displayCompletionBlock != displayCompletionBlock) { - _displayCompletionBlock = displayCompletionBlock; - } - } - - [self setNeedsDisplay]; -} - -#pragma mark Interface State - -- (void)clearContents -{ - [super clearContents]; - - ASDN::MutexLocker l(__instanceLock__); - _weakCacheEntry = nil; // release contents from the cache. -} - -#pragma mark - Cropping - -- (BOOL)isCropEnabled -{ - ASDN::MutexLocker l(__instanceLock__); - return _cropEnabled; -} - -- (void)setCropEnabled:(BOOL)cropEnabled -{ - [self setCropEnabled:cropEnabled recropImmediately:NO inBounds:self.bounds]; -} - -- (void)setCropEnabled:(BOOL)cropEnabled recropImmediately:(BOOL)recropImmediately inBounds:(CGRect)cropBounds -{ - __instanceLock__.lock(); - if (_cropEnabled == cropEnabled) { - __instanceLock__.unlock(); - return; - } - - _cropEnabled = cropEnabled; - _cropDisplayBounds = cropBounds; - - UIImage *image = _image; - __instanceLock__.unlock(); - - // If we have an image to display, display it, respecting our recrop flag. - if (image != nil) { - ASPerformBlockOnMainThread(^{ - if (recropImmediately) - [self displayImmediately]; - else - [self setNeedsDisplay]; - }); - } -} - -- (CGRect)cropRect -{ - ASDN::MutexLocker l(__instanceLock__); - return _cropRect; -} - -- (void)setCropRect:(CGRect)cropRect -{ - { - ASDN::MutexLocker l(__instanceLock__); - if (CGRectEqualToRect(_cropRect, cropRect)) { - return; - } - - _cropRect = cropRect; - } - - // TODO: this logic needs to be updated to respect cropRect. - CGSize boundsSize = self.bounds.size; - CGSize imageSize = self.image.size; - - BOOL isCroppingImage = ((boundsSize.width < imageSize.width) || (boundsSize.height < imageSize.height)); - - // Re-display if we need to. - ASPerformBlockOnMainThread(^{ - if (self.nodeLoaded && self.contentMode == UIViewContentModeScaleAspectFill && isCroppingImage) - [self setNeedsDisplay]; - }); -} - -- (BOOL)forceUpscaling -{ - ASDN::MutexLocker l(__instanceLock__); - return _forceUpscaling; -} - -- (void)setForceUpscaling:(BOOL)forceUpscaling -{ - ASDN::MutexLocker l(__instanceLock__); - _forceUpscaling = forceUpscaling; -} - -- (CGSize)forcedSize -{ - ASDN::MutexLocker l(__instanceLock__); - return _forcedSize; -} - -- (void)setForcedSize:(CGSize)forcedSize -{ - ASDN::MutexLocker l(__instanceLock__); - _forcedSize = forcedSize; -} - -- (asimagenode_modification_block_t)imageModificationBlock -{ - ASDN::MutexLocker l(__instanceLock__); - return _imageModificationBlock; -} - -- (void)setImageModificationBlock:(asimagenode_modification_block_t)imageModificationBlock -{ - ASDN::MutexLocker l(__instanceLock__); - _imageModificationBlock = imageModificationBlock; -} - -#pragma mark - Debug - -- (void)layout -{ - [super layout]; - - if (_debugLabelNode) { - CGSize boundsSize = self.bounds.size; - CGSize debugLabelSize = [_debugLabelNode layoutThatFits:ASSizeRangeMake(CGSizeZero, boundsSize)].size; - CGPoint debugLabelOrigin = CGPointMake(boundsSize.width - debugLabelSize.width, - boundsSize.height - debugLabelSize.height); - _debugLabelNode.frame = (CGRect) {debugLabelOrigin, debugLabelSize}; - } -} - -- (NSDictionary *)debugLabelAttributes -{ - return @{ - NSFontAttributeName: [UIFont systemFontOfSize:15.0], - NSForegroundColorAttributeName: [UIColor redColor] - }; -} - -@end - -#pragma mark - Extras - -asimagenode_modification_block_t ASImageNodeRoundBorderModificationBlock(CGFloat borderWidth, UIColor *borderColor) -{ - return ^(UIImage *originalImage) { - ASGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale); - UIBezierPath *roundOutline = [UIBezierPath bezierPathWithOvalInRect:(CGRect){CGPointZero, originalImage.size}]; - - // Make the image round - [roundOutline addClip]; - - // Draw the original image - [originalImage drawAtPoint:CGPointZero blendMode:kCGBlendModeCopy alpha:1]; - - // Draw a border on top. - if (borderWidth > 0.0) { - [borderColor setStroke]; - [roundOutline setLineWidth:borderWidth]; - [roundOutline stroke]; - } - - return ASGraphicsGetImageAndEndCurrentContext(); - }; -} - -asimagenode_modification_block_t ASImageNodeTintColorModificationBlock(UIColor *color) -{ - return ^(UIImage *originalImage) { - ASGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale); - - // Set color and render template - [color setFill]; - UIImage *templateImage = [originalImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; - [templateImage drawAtPoint:CGPointZero blendMode:kCGBlendModeCopy alpha:1]; - - UIImage *modifiedImage = ASGraphicsGetImageAndEndCurrentContext(); - - // if the original image was stretchy, keep it stretchy - if (!UIEdgeInsetsEqualToEdgeInsets(originalImage.capInsets, UIEdgeInsetsZero)) { - modifiedImage = [modifiedImage resizableImageWithCapInsets:originalImage.capInsets resizingMode:originalImage.resizingMode]; - } - - return modifiedImage; - }; -} diff --git a/submodules/AsyncDisplayKit/Source/Private/ASInternalHelpers.mm b/submodules/AsyncDisplayKit/Source/ASInternalHelpers.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/Private/ASInternalHelpers.mm rename to submodules/AsyncDisplayKit/Source/ASInternalHelpers.mm diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASLayout.mm b/submodules/AsyncDisplayKit/Source/ASLayout.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/Layout/ASLayout.mm rename to submodules/AsyncDisplayKit/Source/ASLayout.mm index 5f09a1a398..6a6a96f24c 100644 --- a/submodules/AsyncDisplayKit/Source/Layout/ASLayout.mm +++ b/submodules/AsyncDisplayKit/Source/ASLayout.mm @@ -14,8 +14,8 @@ #import #import -#import -#import +#import "ASLayoutSpecUtilities.h" +#import "ASLayoutSpec+Subclasses.h" #import #import diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASLayoutElement.mm b/submodules/AsyncDisplayKit/Source/ASLayoutElement.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/Layout/ASLayoutElement.mm rename to submodules/AsyncDisplayKit/Source/ASLayoutElement.mm diff --git a/submodules/AsyncDisplayKit/Source/Private/Layout/ASLayoutElementStylePrivate.h b/submodules/AsyncDisplayKit/Source/ASLayoutElementStylePrivate.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/Private/Layout/ASLayoutElementStylePrivate.h rename to submodules/AsyncDisplayKit/Source/ASLayoutElementStylePrivate.h diff --git a/submodules/AsyncDisplayKit/Source/TextKit/ASLayoutManager.h b/submodules/AsyncDisplayKit/Source/ASLayoutManager.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/TextKit/ASLayoutManager.h rename to submodules/AsyncDisplayKit/Source/ASLayoutManager.h diff --git a/submodules/AsyncDisplayKit/Source/TextKit/ASLayoutManager.mm b/submodules/AsyncDisplayKit/Source/ASLayoutManager.mm similarity index 96% rename from submodules/AsyncDisplayKit/Source/TextKit/ASLayoutManager.mm rename to submodules/AsyncDisplayKit/Source/ASLayoutManager.mm index fbb3b49ea4..9eca28e489 100644 --- a/submodules/AsyncDisplayKit/Source/TextKit/ASLayoutManager.mm +++ b/submodules/AsyncDisplayKit/Source/ASLayoutManager.mm @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import +#import "ASLayoutManager.h" @implementation ASLayoutManager diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASLayoutSpec+Subclasses.h b/submodules/AsyncDisplayKit/Source/ASLayoutSpec+Subclasses.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/Layout/ASLayoutSpec+Subclasses.h rename to submodules/AsyncDisplayKit/Source/ASLayoutSpec+Subclasses.h diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASLayoutSpec+Subclasses.mm b/submodules/AsyncDisplayKit/Source/ASLayoutSpec+Subclasses.mm similarity index 95% rename from submodules/AsyncDisplayKit/Source/Layout/ASLayoutSpec+Subclasses.mm rename to submodules/AsyncDisplayKit/Source/ASLayoutSpec+Subclasses.mm index 17ff53e6c5..c1e4e2496a 100644 --- a/submodules/AsyncDisplayKit/Source/Layout/ASLayoutSpec+Subclasses.mm +++ b/submodules/AsyncDisplayKit/Source/ASLayoutSpec+Subclasses.mm @@ -7,10 +7,10 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import +#import "ASLayoutSpec+Subclasses.h" #import -#import +#import "ASLayoutSpecPrivate.h" #pragma mark - ASNullLayoutSpec diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASLayoutSpec.mm b/submodules/AsyncDisplayKit/Source/ASLayoutSpec.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/Layout/ASLayoutSpec.mm rename to submodules/AsyncDisplayKit/Source/ASLayoutSpec.mm index 8da22d3d01..6123e4d734 100644 --- a/submodules/AsyncDisplayKit/Source/Layout/ASLayoutSpec.mm +++ b/submodules/AsyncDisplayKit/Source/ASLayoutSpec.mm @@ -8,12 +8,12 @@ // #import -#import +#import "ASLayoutSpecPrivate.h" -#import +#import "ASLayoutSpec+Subclasses.h" #import -#import +#import "ASLayoutElementStylePrivate.h" #import #import #import diff --git a/submodules/AsyncDisplayKit/Source/Private/Layout/ASLayoutSpecPrivate.h b/submodules/AsyncDisplayKit/Source/ASLayoutSpecPrivate.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/Private/Layout/ASLayoutSpecPrivate.h rename to submodules/AsyncDisplayKit/Source/ASLayoutSpecPrivate.h diff --git a/submodules/AsyncDisplayKit/Source/Private/Layout/ASLayoutSpecUtilities.h b/submodules/AsyncDisplayKit/Source/ASLayoutSpecUtilities.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/Private/Layout/ASLayoutSpecUtilities.h rename to submodules/AsyncDisplayKit/Source/ASLayoutSpecUtilities.h diff --git a/submodules/AsyncDisplayKit/Source/Private/ASLayoutTransition.h b/submodules/AsyncDisplayKit/Source/ASLayoutTransition.h similarity index 97% rename from submodules/AsyncDisplayKit/Source/Private/ASLayoutTransition.h rename to submodules/AsyncDisplayKit/Source/ASLayoutTransition.h index f2bcf9b253..d11eb65fb1 100644 --- a/submodules/AsyncDisplayKit/Source/Private/ASLayoutTransition.h +++ b/submodules/AsyncDisplayKit/Source/ASLayoutTransition.h @@ -7,9 +7,10 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // +#import #import #import -#import +#import "ASDisplayNodeLayout.h" #import #import diff --git a/submodules/AsyncDisplayKit/Source/Private/ASLayoutTransition.mm b/submodules/AsyncDisplayKit/Source/ASLayoutTransition.mm similarity index 96% rename from submodules/AsyncDisplayKit/Source/Private/ASLayoutTransition.mm rename to submodules/AsyncDisplayKit/Source/ASLayoutTransition.mm index 2fea2b8ad0..0956943cc0 100644 --- a/submodules/AsyncDisplayKit/Source/Private/ASLayoutTransition.mm +++ b/submodules/AsyncDisplayKit/Source/ASLayoutTransition.mm @@ -7,13 +7,12 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import +#import "ASLayoutTransition.h" #import #import -#import // Required for _insertSubnode... / _removeFromSupernode. -#import +#import "ASDisplayNodeInternal.h" // Required for _insertSubnode... / _removeFromSupernode. #import @@ -98,7 +97,6 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) { [self calculateSubnodeOperationsIfNeeded]; // Create an activity even if no subnodes affected. - as_activity_create_for_scope("Apply subnode insertions and moves"); if (_insertedSubnodePositions.size() == 0 && _subnodeMoves.size() == 0) { return; } @@ -131,7 +129,6 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) { - (void)applySubnodeRemovals { - as_activity_scope(as_activity_create("Apply subnode removals", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT)); MutexLocker l(*__instanceLock__); [self calculateSubnodeOperationsIfNeeded]; @@ -158,7 +155,6 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) { } // Create an activity even if no subnodes affected. - as_activity_create_for_scope("Calculate subnode operations"); ASLayout *previousLayout = _previousLayout.layout; ASLayout *pendingLayout = _pendingLayout.layout; diff --git a/submodules/AsyncDisplayKit/Source/Details/ASMainSerialQueue.h b/submodules/AsyncDisplayKit/Source/ASMainSerialQueue.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/Details/ASMainSerialQueue.h rename to submodules/AsyncDisplayKit/Source/ASMainSerialQueue.h diff --git a/submodules/AsyncDisplayKit/Source/Details/ASMainSerialQueue.mm b/submodules/AsyncDisplayKit/Source/ASMainSerialQueue.mm similarity index 96% rename from submodules/AsyncDisplayKit/Source/Details/ASMainSerialQueue.mm rename to submodules/AsyncDisplayKit/Source/ASMainSerialQueue.mm index 06cb7c3b9b..60bf8d7f67 100644 --- a/submodules/AsyncDisplayKit/Source/Details/ASMainSerialQueue.mm +++ b/submodules/AsyncDisplayKit/Source/ASMainSerialQueue.mm @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import +#import "ASMainSerialQueue.h" #import #import diff --git a/submodules/AsyncDisplayKit/Source/ASMainThreadDeallocation.mm b/submodules/AsyncDisplayKit/Source/ASMainThreadDeallocation.mm index 1cf90ada05..4b16c932d2 100644 --- a/submodules/AsyncDisplayKit/Source/ASMainThreadDeallocation.mm +++ b/submodules/AsyncDisplayKit/Source/ASMainThreadDeallocation.mm @@ -11,7 +11,6 @@ #import #import #import -#import #import #import @@ -42,15 +41,12 @@ } if ([object_getClass(value) needsMainThreadDeallocation]) { - as_log_debug(ASMainThreadDeallocationLog(), "%@: Trampolining ivar '%s' value %@ for main deallocation.", self, ivar_getName(ivar), value); - // Release the ivar's reference before handing the object to the queue so we // don't risk holding onto it longer than the queue does. object_setIvar(self, ivar, nil); ASPerformMainThreadDeallocation(&value); } else { - as_log_debug(ASMainThreadDeallocationLog(), "%@: Not trampolining ivar '%s' value %@.", self, ivar_getName(ivar), value); } } } @@ -112,16 +108,13 @@ // If it's `id` we have to include it just in case. resultIvars[resultCount] = ivar; resultCount += 1; - as_log_verbose(ASMainThreadDeallocationLog(), "%@: Marking ivar '%s' for possible main deallocation due to type id", self, ivar_getName(ivar)); } else { // If it's an ivar with a static type, check the type. Class c = ASGetClassFromType(type); if ([c needsMainThreadDeallocation]) { resultIvars[resultCount] = ivar; resultCount += 1; - as_log_verbose(ASMainThreadDeallocationLog(), "%@: Marking ivar '%s' for main deallocation due to class %@", self, ivar_getName(ivar), c); } else { - as_log_verbose(ASMainThreadDeallocationLog(), "%@: Skipping ivar '%s' for main deallocation.", self, ivar_getName(ivar)); } } } diff --git a/submodules/AsyncDisplayKit/Source/ASMapNode.h b/submodules/AsyncDisplayKit/Source/ASMapNode.h deleted file mode 100644 index 9ddc2b6ffc..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASMapNode.h +++ /dev/null @@ -1,92 +0,0 @@ -// -// ASMapNode.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -#if TARGET_OS_IOS && AS_USE_MAPKIT -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * Map Annotation options. - * The default behavior is to ignore the annotations' positions, use the region or options specified instead. - * Swift: to select the default behavior, use []. - */ -typedef NS_OPTIONS(NSUInteger, ASMapNodeShowAnnotationsOptions) -{ - /** The annotations' positions are ignored, use the region or options specified instead. */ - ASMapNodeShowAnnotationsOptionsIgnored = 0, - /** The annotations' positions are used to calculate the region to show in the map, equivalent to showAnnotations:animated. */ - ASMapNodeShowAnnotationsOptionsZoomed = 1 << 0, - /** This will only have an effect if combined with the Zoomed state with liveMap turned on.*/ - ASMapNodeShowAnnotationsOptionsAnimated = 1 << 1 -}; - -@interface ASMapNode : ASImageNode - -/** - The current options of ASMapNode. This can be set at any time and ASMapNode will animate the change.

This property may be set from a background thread before the node is loaded, and will automatically be applied to define the behavior of the static snapshot (if .liveMap = NO) or the internal MKMapView (otherwise).

Changes to the region and camera options will only be animated when when the liveMap mode is enabled, otherwise these options will be applied statically to the new snapshot.

The options object is used to specify properties even when the liveMap mode is enabled, allowing seamless transitions between the snapshot and liveMap (as well as back to the snapshot). - */ -@property (nonatomic) MKMapSnapshotOptions *options; - -/** The region is simply the sub-field on the options object. If the objects object is reset, - this will in effect be overwritten and become the value of the .region property on that object. - Defaults to MKCoordinateRegionForMapRect(MKMapRectWorld). - */ -@property (nonatomic) MKCoordinateRegion region; - -/** - This is the MKMapView that is the live map part of ASMapNode. This will be nil if .liveMap = NO. Note, MKMapView is *not* thread-safe. - */ -@property (nullable, readonly) MKMapView *mapView; - -/** - Set this to YES to turn the snapshot into an interactive MKMapView and vice versa. Defaults to NO. This property may be set on a background thread before the node is loaded, and will automatically be actioned, once the node is loaded. - */ -@property (getter=isLiveMap) BOOL liveMap; - -/** - @abstract Whether ASMapNode should automatically request a new map snapshot to correspond to the new node size. - @default Default value is YES. - @discussion If mapSize is set then this will be set to NO, since the size will be the same in all orientations. - */ -@property BOOL needsMapReloadOnBoundsChange; - -/** - Set the delegate of the MKMapView. This can be set even before mapView is created and will be set on the map in the case that the liveMap mode is engaged. - - If the live map view has been created, this may only be set on the main thread. - */ -@property (nonatomic, weak) id mapDelegate; - -/** - * @abstract The annotations to display on the map. - */ -@property (copy) NSArray> *annotations; - -/** - * @abstract This property specifies how to show the annotations. - * @default Default value is ASMapNodeShowAnnotationsIgnored - */ -@property ASMapNodeShowAnnotationsOptions showAnnotationsOptions; - -/** - * @abstract The block which should return annotation image for static map based on provided annotation. - * @discussion This block is executed on an arbitrary serial queue. If this block is nil, standard pin is used. - */ -@property (nullable) UIImage * _Nullable (^imageForStaticMapAnnotationBlock)(id annotation, CGPoint *centerOffset); - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASMapNode.h.orig b/submodules/AsyncDisplayKit/Source/ASMapNode.h.orig deleted file mode 100644 index 9ae005aa43..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASMapNode.h.orig +++ /dev/null @@ -1,99 +0,0 @@ -// -// ASMapNode.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -<<<<<<< HEAD -#ifndef MINIMAL_ASDK - -======= -#import -#import - -#if TARGET_OS_IOS && AS_USE_MAPKIT ->>>>>>> 565da7d4935740d12fc204aa061faf093831da1e -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * Map Annotation options. - * The default behavior is to ignore the annotations' positions, use the region or options specified instead. - * Swift: to select the default behavior, use []. - */ -typedef NS_OPTIONS(NSUInteger, ASMapNodeShowAnnotationsOptions) -{ - /** The annotations' positions are ignored, use the region or options specified instead. */ - ASMapNodeShowAnnotationsOptionsIgnored = 0, - /** The annotations' positions are used to calculate the region to show in the map, equivalent to showAnnotations:animated. */ - ASMapNodeShowAnnotationsOptionsZoomed = 1 << 0, - /** This will only have an effect if combined with the Zoomed state with liveMap turned on.*/ - ASMapNodeShowAnnotationsOptionsAnimated = 1 << 1 -}; - -@interface ASMapNode : ASImageNode - -/** - The current options of ASMapNode. This can be set at any time and ASMapNode will animate the change.

This property may be set from a background thread before the node is loaded, and will automatically be applied to define the behavior of the static snapshot (if .liveMap = NO) or the internal MKMapView (otherwise).

Changes to the region and camera options will only be animated when when the liveMap mode is enabled, otherwise these options will be applied statically to the new snapshot.

The options object is used to specify properties even when the liveMap mode is enabled, allowing seamless transitions between the snapshot and liveMap (as well as back to the snapshot). - */ -@property (nonatomic) MKMapSnapshotOptions *options; - -/** The region is simply the sub-field on the options object. If the objects object is reset, - this will in effect be overwritten and become the value of the .region property on that object. - Defaults to MKCoordinateRegionForMapRect(MKMapRectWorld). - */ -@property (nonatomic) MKCoordinateRegion region; - -/** - This is the MKMapView that is the live map part of ASMapNode. This will be nil if .liveMap = NO. Note, MKMapView is *not* thread-safe. - */ -@property (nullable, readonly) MKMapView *mapView; - -/** - Set this to YES to turn the snapshot into an interactive MKMapView and vice versa. Defaults to NO. This property may be set on a background thread before the node is loaded, and will automatically be actioned, once the node is loaded. - */ -@property (getter=isLiveMap) BOOL liveMap; - -/** - @abstract Whether ASMapNode should automatically request a new map snapshot to correspond to the new node size. - @default Default value is YES. - @discussion If mapSize is set then this will be set to NO, since the size will be the same in all orientations. - */ -@property BOOL needsMapReloadOnBoundsChange; - -/** - Set the delegate of the MKMapView. This can be set even before mapView is created and will be set on the map in the case that the liveMap mode is engaged. - - If the live map view has been created, this may only be set on the main thread. - */ -@property (nonatomic, weak) id mapDelegate; - -/** - * @abstract The annotations to display on the map. - */ -@property (copy) NSArray> *annotations; - -/** - * @abstract This property specifies how to show the annotations. - * @default Default value is ASMapNodeShowAnnotationsIgnored - */ -@property ASMapNodeShowAnnotationsOptions showAnnotationsOptions; - -/** - * @abstract The block which should return annotation image for static map based on provided annotation. - * @discussion This block is executed on an arbitrary serial queue. If this block is nil, standard pin is used. - */ -@property (nullable) UIImage * _Nullable (^imageForStaticMapAnnotationBlock)(id annotation, CGPoint *centerOffset); - -@end - -NS_ASSUME_NONNULL_END - -#endif - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASMapNode.mm b/submodules/AsyncDisplayKit/Source/ASMapNode.mm deleted file mode 100644 index 1c204ab974..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASMapNode.mm +++ /dev/null @@ -1,442 +0,0 @@ -// -// ASMapNode.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if TARGET_OS_IOS && AS_USE_MAPKIT - -#import - -#import -#import -#import -#import -#import -#import -#import - -@interface ASMapNode() -{ - MKMapSnapshotter *_snapshotter; - BOOL _snapshotAfterLayout; - NSArray *_annotations; -} -@end - -@implementation ASMapNode - -@synthesize needsMapReloadOnBoundsChange = _needsMapReloadOnBoundsChange; -@synthesize mapDelegate = _mapDelegate; -@synthesize options = _options; -@synthesize liveMap = _liveMap; -@synthesize showAnnotationsOptions = _showAnnotationsOptions; - -#pragma mark - Lifecycle -- (instancetype)init -{ - if (!(self = [super init])) { - return nil; - } - self.backgroundColor = ASDisplayNodeDefaultPlaceholderColor(); - self.clipsToBounds = YES; - self.userInteractionEnabled = YES; - - _needsMapReloadOnBoundsChange = YES; - _liveMap = NO; - _annotations = @[]; - _showAnnotationsOptions = ASMapNodeShowAnnotationsOptionsIgnored; - return self; -} - -- (void)didLoad -{ - [super didLoad]; - if (self.isLiveMap) { - [self addLiveMap]; - } -} - -- (void)dealloc -{ - [self destroySnapshotter]; -} - -- (void)setLayerBacked:(BOOL)layerBacked -{ - ASDisplayNodeAssert(!self.isLiveMap, @"ASMapNode can not be layer backed whilst .liveMap = YES, set .liveMap = NO to use layer backing."); - [super setLayerBacked:layerBacked]; -} - -- (void)didEnterPreloadState -{ - [super didEnterPreloadState]; - ASPerformBlockOnMainThread(^{ - if (self.isLiveMap) { - [self addLiveMap]; - } else { - [self takeSnapshot]; - } - }); -} - -- (void)didExitPreloadState -{ - [super didExitPreloadState]; - ASPerformBlockOnMainThread(^{ - if (self.isLiveMap) { - [self removeLiveMap]; - } - }); -} - -#pragma mark - Settings - -- (BOOL)isLiveMap -{ - ASLockScopeSelf(); - return _liveMap; -} - -- (void)setLiveMap:(BOOL)liveMap -{ - ASDisplayNodeAssert(!self.isLayerBacked, @"ASMapNode can not use the interactive map feature whilst .isLayerBacked = YES, set .layerBacked = NO to use the interactive map feature."); - ASLockScopeSelf(); - if (liveMap == _liveMap) { - return; - } - _liveMap = liveMap; - if (self.nodeLoaded) { - liveMap ? [self addLiveMap] : [self removeLiveMap]; - } -} - -- (BOOL)needsMapReloadOnBoundsChange -{ - ASLockScopeSelf(); - return _needsMapReloadOnBoundsChange; -} - -- (void)setNeedsMapReloadOnBoundsChange:(BOOL)needsMapReloadOnBoundsChange -{ - ASLockScopeSelf(); - _needsMapReloadOnBoundsChange = needsMapReloadOnBoundsChange; -} - -- (MKMapSnapshotOptions *)options -{ - ASLockScopeSelf(); - if (!_options) { - _options = [[MKMapSnapshotOptions alloc] init]; - _options.region = MKCoordinateRegionForMapRect(MKMapRectWorld); - CGSize calculatedSize = self.calculatedSize; - if (!CGSizeEqualToSize(calculatedSize, CGSizeZero)) { - _options.size = calculatedSize; - } - } - return _options; -} - -- (void)setOptions:(MKMapSnapshotOptions *)options -{ - ASLockScopeSelf(); - if (!_options || ![options isEqual:_options]) { - _options = options; - if (self.isLiveMap) { - [self applySnapshotOptions]; - } else if (_snapshotter) { - [self destroySnapshotter]; - [self takeSnapshot]; - } - } -} - -- (MKCoordinateRegion)region -{ - return self.options.region; -} - -- (void)setRegion:(MKCoordinateRegion)region -{ - MKMapSnapshotOptions * options = [self.options copy]; - options.region = region; - self.options = options; -} - -- (id)mapDelegate -{ - return ASLockedSelf(_mapDelegate); -} - -- (void)setMapDelegate:(id)mapDelegate { - ASLockScopeSelf(); - _mapDelegate = mapDelegate; - - if (_mapView) { - ASDisplayNodeAssertMainThread(); - _mapView.delegate = mapDelegate; - } -} - -#pragma mark - Snapshotter - -- (void)takeSnapshot -{ - // If our size is zero, we want to avoid calling a default sized snapshot. Set _snapshotAfterLayout to YES - // so if layout changes in the future, we'll try snapshotting again. - ASLayout *layout = self.calculatedLayout; - if (layout == nil || CGSizeEqualToSize(CGSizeZero, layout.size)) { - _snapshotAfterLayout = YES; - return; - } - - _snapshotAfterLayout = NO; - - if (!_snapshotter) { - [self setUpSnapshotter]; - } - - if (_snapshotter.isLoading) { - return; - } - - __weak __typeof__(self) weakSelf = self; - [_snapshotter startWithQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) - completionHandler:^(MKMapSnapshot *snapshot, NSError *error) { - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - - if (!error) { - UIImage *image = snapshot.image; - NSArray *annotations = strongSelf.annotations; - if (annotations.count > 0) { - // Only create a graphics context if we have annotations to draw. - // The MKMapSnapshotter is currently not capable of rendering annotations automatically. - - CGRect finalImageRect = CGRectMake(0, 0, image.size.width, image.size.height); - - ASGraphicsBeginImageContextWithOptions(image.size, YES, image.scale); - [image drawAtPoint:CGPointZero]; - - UIImage *pinImage; - CGPoint pinCenterOffset = CGPointZero; - - // Get a standard annotation view pin if there is no custom annotation block. - if (!strongSelf.imageForStaticMapAnnotationBlock) { - pinImage = [strongSelf.class defaultPinImageWithCenterOffset:&pinCenterOffset]; - } - - for (id annotation in annotations) { - if (strongSelf.imageForStaticMapAnnotationBlock) { - // Get custom annotation image from custom annotation block. - pinImage = strongSelf.imageForStaticMapAnnotationBlock(annotation, &pinCenterOffset); - if (!pinImage) { - // just for case block returned nil, which can happen - pinImage = [strongSelf.class defaultPinImageWithCenterOffset:&pinCenterOffset]; - } - } - - CGPoint point = [snapshot pointForCoordinate:annotation.coordinate]; - if (CGRectContainsPoint(finalImageRect, point)) { - CGSize pinSize = pinImage.size; - point.x -= pinSize.width / 2.0; - point.y -= pinSize.height / 2.0; - point.x += pinCenterOffset.x; - point.y += pinCenterOffset.y; - [pinImage drawAtPoint:point]; - } - } - - image = ASGraphicsGetImageAndEndCurrentContext(); - } - - strongSelf.image = image; - } - }]; -} - -+ (UIImage *)defaultPinImageWithCenterOffset:(CGPoint *)centerOffset NS_RETURNS_RETAINED -{ - static MKAnnotationView *pin; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - pin = [[MKPinAnnotationView alloc] initWithAnnotation:nil reuseIdentifier:@""]; - }); - *centerOffset = pin.centerOffset; - return pin.image; -} - -- (void)setUpSnapshotter -{ - _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:self.options]; -} - -- (void)destroySnapshotter -{ - [_snapshotter cancel]; - _snapshotter = nil; -} - -- (void)applySnapshotOptions -{ - MKMapSnapshotOptions *options = self.options; - [_mapView setCamera:options.camera animated:YES]; - [_mapView setRegion:options.region animated:YES]; - [_mapView setMapType:options.mapType]; - _mapView.showsBuildings = options.showsBuildings; - _mapView.showsPointsOfInterest = options.showsPointsOfInterest; -} - -#pragma mark - Actions -- (void)addLiveMap -{ - ASDisplayNodeAssertMainThread(); - if (!_mapView) { - __weak ASMapNode *weakSelf = self; - _mapView = [[MKMapView alloc] initWithFrame:CGRectZero]; - _mapView.delegate = weakSelf.mapDelegate; - [weakSelf applySnapshotOptions]; - [_mapView addAnnotations:_annotations]; - [weakSelf setNeedsLayout]; - [weakSelf.view addSubview:_mapView]; - - ASMapNodeShowAnnotationsOptions showAnnotationsOptions = self.showAnnotationsOptions; - if (showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsZoomed) { - BOOL const animated = showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsAnimated; - [_mapView showAnnotations:_mapView.annotations animated:animated]; - } - } -} - -- (void)removeLiveMap -{ - [_mapView removeFromSuperview]; - _mapView = nil; -} - -- (NSArray *)annotations -{ - ASLockScopeSelf(); - return _annotations; -} - -- (void)setAnnotations:(NSArray *)annotations -{ - annotations = [annotations copy] ? : @[]; - - ASLockScopeSelf(); - _annotations = annotations; - ASMapNodeShowAnnotationsOptions showAnnotationsOptions = self.showAnnotationsOptions; - if (self.isLiveMap) { - [_mapView removeAnnotations:_mapView.annotations]; - [_mapView addAnnotations:annotations]; - - if (showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsZoomed) { - BOOL const animated = showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsAnimated; - [_mapView showAnnotations:_mapView.annotations animated:animated]; - } - } else { - if (showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsZoomed) { - self.region = [self regionToFitAnnotations:annotations]; - } - else { - [self takeSnapshot]; - } - } -} - -- (MKCoordinateRegion)regionToFitAnnotations:(NSArray> *)annotations -{ - if([annotations count] == 0) - return MKCoordinateRegionForMapRect(MKMapRectWorld); - - CLLocationCoordinate2D topLeftCoord = CLLocationCoordinate2DMake(-90, 180); - CLLocationCoordinate2D bottomRightCoord = CLLocationCoordinate2DMake(90, -180); - - for (id annotation in annotations) { - topLeftCoord = CLLocationCoordinate2DMake(std::fmax(topLeftCoord.latitude, annotation.coordinate.latitude), - std::fmin(topLeftCoord.longitude, annotation.coordinate.longitude)); - bottomRightCoord = CLLocationCoordinate2DMake(std::fmin(bottomRightCoord.latitude, annotation.coordinate.latitude), - std::fmax(bottomRightCoord.longitude, annotation.coordinate.longitude)); - } - - MKCoordinateRegion region = MKCoordinateRegionMake(CLLocationCoordinate2DMake(topLeftCoord.latitude - (topLeftCoord.latitude - bottomRightCoord.latitude) * 0.5, - topLeftCoord.longitude + (bottomRightCoord.longitude - topLeftCoord.longitude) * 0.5), - MKCoordinateSpanMake(std::fabs(topLeftCoord.latitude - bottomRightCoord.latitude) * 2, - std::fabs(bottomRightCoord.longitude - topLeftCoord.longitude) * 2)); - - return region; -} - --(ASMapNodeShowAnnotationsOptions)showAnnotationsOptions { - return ASLockedSelf(_showAnnotationsOptions); -} - --(void)setShowAnnotationsOptions:(ASMapNodeShowAnnotationsOptions)showAnnotationsOptions { - ASLockScopeSelf(); - _showAnnotationsOptions = showAnnotationsOptions; -} - -#pragma mark - Layout -- (void)setSnapshotSizeWithReloadIfNeeded:(CGSize)snapshotSize -{ - if (snapshotSize.height > 0 && snapshotSize.width > 0 && !CGSizeEqualToSize(self.options.size, snapshotSize)) { - _options.size = snapshotSize; - if (_snapshotter) { - [self destroySnapshotter]; - [self takeSnapshot]; - } - } -} - -- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize -{ - // FIXME: Need a better way to allow maps to take up the right amount of space in a layout (sizeRange, etc) - // These fallbacks protect against inheriting a constrainedSize that contains a CGFLOAT_MAX value. - if (!ASIsCGSizeValidForLayout(constrainedSize)) { - //ASDisplayNodeAssert(NO, @"Invalid width or height in ASMapNode"); - constrainedSize = CGSizeZero; - } - [self setSnapshotSizeWithReloadIfNeeded:constrainedSize]; - return constrainedSize; -} - -- (void)calculatedLayoutDidChange -{ - [super calculatedLayoutDidChange]; - - if (_snapshotAfterLayout) { - [self takeSnapshot]; - } -} - -// -layout isn't usually needed over -layoutSpecThatFits, but this way we can avoid a needless node wrapper for MKMapView. -- (void)layout -{ - [super layout]; - if (self.isLiveMap) { - _mapView.frame = CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height); - } else { - // If our bounds.size is different from our current snapshot size, then let's request a new image from MKMapSnapshotter. - if (_needsMapReloadOnBoundsChange) { - [self setSnapshotSizeWithReloadIfNeeded:self.bounds.size]; - // FIXME: Adding a check for Preload here seems to cause intermittent map load failures, but shouldn't. - // if (ASInterfaceStateIncludesPreload(self.interfaceState)) { - } - } -} - -- (BOOL)supportsLayerBacking -{ - return NO; -} - -@end -#endif // TARGET_OS_IOS && AS_USE_MAPKIT diff --git a/submodules/AsyncDisplayKit/Source/ASMapNode.mm.orig b/submodules/AsyncDisplayKit/Source/ASMapNode.mm.orig deleted file mode 100644 index 1014f2f32c..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASMapNode.mm.orig +++ /dev/null @@ -1,456 +0,0 @@ -// -// ASMapNode.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -<<<<<<< HEAD -#ifndef MINIMAL_ASDK - -#import - -#if TARGET_OS_IOS -======= ->>>>>>> 565da7d4935740d12fc204aa061faf093831da1e -#import - -#if TARGET_OS_IOS && AS_USE_MAPKIT - -#import - -#import -#import -#import -#import -#import -#import -#import - -@interface ASMapNode() -{ - MKMapSnapshotter *_snapshotter; - BOOL _snapshotAfterLayout; - NSArray *_annotations; -} -@end - -@implementation ASMapNode - -@synthesize needsMapReloadOnBoundsChange = _needsMapReloadOnBoundsChange; -@synthesize mapDelegate = _mapDelegate; -@synthesize options = _options; -@synthesize liveMap = _liveMap; -@synthesize showAnnotationsOptions = _showAnnotationsOptions; - -#pragma mark - Lifecycle -- (instancetype)init -{ - if (!(self = [super init])) { - return nil; - } - self.backgroundColor = ASDisplayNodeDefaultPlaceholderColor(); - self.clipsToBounds = YES; - self.userInteractionEnabled = YES; - - _needsMapReloadOnBoundsChange = YES; - _liveMap = NO; - _annotations = @[]; - _showAnnotationsOptions = ASMapNodeShowAnnotationsOptionsIgnored; - return self; -} - -- (void)didLoad -{ - [super didLoad]; - if (self.isLiveMap) { - [self addLiveMap]; - } -} - -- (void)dealloc -{ - [self destroySnapshotter]; -} - -- (void)setLayerBacked:(BOOL)layerBacked -{ - ASDisplayNodeAssert(!self.isLiveMap, @"ASMapNode can not be layer backed whilst .liveMap = YES, set .liveMap = NO to use layer backing."); - [super setLayerBacked:layerBacked]; -} - -- (void)didEnterPreloadState -{ - [super didEnterPreloadState]; - ASPerformBlockOnMainThread(^{ - if (self.isLiveMap) { - [self addLiveMap]; - } else { - [self takeSnapshot]; - } - }); -} - -- (void)didExitPreloadState -{ - [super didExitPreloadState]; - ASPerformBlockOnMainThread(^{ - if (self.isLiveMap) { - [self removeLiveMap]; - } - }); -} - -#pragma mark - Settings - -- (BOOL)isLiveMap -{ - ASLockScopeSelf(); - return _liveMap; -} - -- (void)setLiveMap:(BOOL)liveMap -{ - ASDisplayNodeAssert(!self.isLayerBacked, @"ASMapNode can not use the interactive map feature whilst .isLayerBacked = YES, set .layerBacked = NO to use the interactive map feature."); - ASLockScopeSelf(); - if (liveMap == _liveMap) { - return; - } - _liveMap = liveMap; - if (self.nodeLoaded) { - liveMap ? [self addLiveMap] : [self removeLiveMap]; - } -} - -- (BOOL)needsMapReloadOnBoundsChange -{ - ASLockScopeSelf(); - return _needsMapReloadOnBoundsChange; -} - -- (void)setNeedsMapReloadOnBoundsChange:(BOOL)needsMapReloadOnBoundsChange -{ - ASLockScopeSelf(); - _needsMapReloadOnBoundsChange = needsMapReloadOnBoundsChange; -} - -- (MKMapSnapshotOptions *)options -{ - ASLockScopeSelf(); - if (!_options) { - _options = [[MKMapSnapshotOptions alloc] init]; - _options.region = MKCoordinateRegionForMapRect(MKMapRectWorld); - CGSize calculatedSize = self.calculatedSize; - if (!CGSizeEqualToSize(calculatedSize, CGSizeZero)) { - _options.size = calculatedSize; - } - } - return _options; -} - -- (void)setOptions:(MKMapSnapshotOptions *)options -{ - ASLockScopeSelf(); - if (!_options || ![options isEqual:_options]) { - _options = options; - if (self.isLiveMap) { - [self applySnapshotOptions]; - } else if (_snapshotter) { - [self destroySnapshotter]; - [self takeSnapshot]; - } - } -} - -- (MKCoordinateRegion)region -{ - return self.options.region; -} - -- (void)setRegion:(MKCoordinateRegion)region -{ - MKMapSnapshotOptions * options = [self.options copy]; - options.region = region; - self.options = options; -} - -- (id)mapDelegate -{ - return ASLockedSelf(_mapDelegate); -} - -- (void)setMapDelegate:(id)mapDelegate { - ASLockScopeSelf(); - _mapDelegate = mapDelegate; - - if (_mapView) { - ASDisplayNodeAssertMainThread(); - _mapView.delegate = mapDelegate; - } -} - -#pragma mark - Snapshotter - -- (void)takeSnapshot -{ - // If our size is zero, we want to avoid calling a default sized snapshot. Set _snapshotAfterLayout to YES - // so if layout changes in the future, we'll try snapshotting again. - ASLayout *layout = self.calculatedLayout; - if (layout == nil || CGSizeEqualToSize(CGSizeZero, layout.size)) { - _snapshotAfterLayout = YES; - return; - } - - _snapshotAfterLayout = NO; - - if (!_snapshotter) { - [self setUpSnapshotter]; - } - - if (_snapshotter.isLoading) { - return; - } - - __weak __typeof__(self) weakSelf = self; - [_snapshotter startWithQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) - completionHandler:^(MKMapSnapshot *snapshot, NSError *error) { - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - - if (!error) { - UIImage *image = snapshot.image; - NSArray *annotations = strongSelf.annotations; - if (annotations.count > 0) { - // Only create a graphics context if we have annotations to draw. - // The MKMapSnapshotter is currently not capable of rendering annotations automatically. - - CGRect finalImageRect = CGRectMake(0, 0, image.size.width, image.size.height); - - ASGraphicsBeginImageContextWithOptions(image.size, YES, image.scale); - [image drawAtPoint:CGPointZero]; - - UIImage *pinImage; - CGPoint pinCenterOffset = CGPointZero; - - // Get a standard annotation view pin if there is no custom annotation block. - if (!strongSelf.imageForStaticMapAnnotationBlock) { - pinImage = [strongSelf.class defaultPinImageWithCenterOffset:&pinCenterOffset]; - } - - for (id annotation in annotations) { - if (strongSelf.imageForStaticMapAnnotationBlock) { - // Get custom annotation image from custom annotation block. - pinImage = strongSelf.imageForStaticMapAnnotationBlock(annotation, &pinCenterOffset); - if (!pinImage) { - // just for case block returned nil, which can happen - pinImage = [strongSelf.class defaultPinImageWithCenterOffset:&pinCenterOffset]; - } - } - - CGPoint point = [snapshot pointForCoordinate:annotation.coordinate]; - if (CGRectContainsPoint(finalImageRect, point)) { - CGSize pinSize = pinImage.size; - point.x -= pinSize.width / 2.0; - point.y -= pinSize.height / 2.0; - point.x += pinCenterOffset.x; - point.y += pinCenterOffset.y; - [pinImage drawAtPoint:point]; - } - } - - image = ASGraphicsGetImageAndEndCurrentContext(); - } - - strongSelf.image = image; - } - }]; -} - -+ (UIImage *)defaultPinImageWithCenterOffset:(CGPoint *)centerOffset NS_RETURNS_RETAINED -{ - static MKAnnotationView *pin; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - pin = [[MKPinAnnotationView alloc] initWithAnnotation:nil reuseIdentifier:@""]; - }); - *centerOffset = pin.centerOffset; - return pin.image; -} - -- (void)setUpSnapshotter -{ - _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:self.options]; -} - -- (void)destroySnapshotter -{ - [_snapshotter cancel]; - _snapshotter = nil; -} - -- (void)applySnapshotOptions -{ - MKMapSnapshotOptions *options = self.options; - [_mapView setCamera:options.camera animated:YES]; - [_mapView setRegion:options.region animated:YES]; - [_mapView setMapType:options.mapType]; - _mapView.showsBuildings = options.showsBuildings; - _mapView.showsPointsOfInterest = options.showsPointsOfInterest; -} - -#pragma mark - Actions -- (void)addLiveMap -{ - ASDisplayNodeAssertMainThread(); - if (!_mapView) { - __weak ASMapNode *weakSelf = self; - _mapView = [[MKMapView alloc] initWithFrame:CGRectZero]; - _mapView.delegate = weakSelf.mapDelegate; - [weakSelf applySnapshotOptions]; - [_mapView addAnnotations:_annotations]; - [weakSelf setNeedsLayout]; - [weakSelf.view addSubview:_mapView]; - - ASMapNodeShowAnnotationsOptions showAnnotationsOptions = self.showAnnotationsOptions; - if (showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsZoomed) { - BOOL const animated = showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsAnimated; - [_mapView showAnnotations:_mapView.annotations animated:animated]; - } - } -} - -- (void)removeLiveMap -{ - [_mapView removeFromSuperview]; - _mapView = nil; -} - -- (NSArray *)annotations -{ - ASLockScopeSelf(); - return _annotations; -} - -- (void)setAnnotations:(NSArray *)annotations -{ - annotations = [annotations copy] ? : @[]; - - ASLockScopeSelf(); - _annotations = annotations; - ASMapNodeShowAnnotationsOptions showAnnotationsOptions = self.showAnnotationsOptions; - if (self.isLiveMap) { - [_mapView removeAnnotations:_mapView.annotations]; - [_mapView addAnnotations:annotations]; - - if (showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsZoomed) { - BOOL const animated = showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsAnimated; - [_mapView showAnnotations:_mapView.annotations animated:animated]; - } - } else { - if (showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsZoomed) { - self.region = [self regionToFitAnnotations:annotations]; - } - else { - [self takeSnapshot]; - } - } -} - -- (MKCoordinateRegion)regionToFitAnnotations:(NSArray> *)annotations -{ - if([annotations count] == 0) - return MKCoordinateRegionForMapRect(MKMapRectWorld); - - CLLocationCoordinate2D topLeftCoord = CLLocationCoordinate2DMake(-90, 180); - CLLocationCoordinate2D bottomRightCoord = CLLocationCoordinate2DMake(90, -180); - - for (id annotation in annotations) { - topLeftCoord = CLLocationCoordinate2DMake(std::fmax(topLeftCoord.latitude, annotation.coordinate.latitude), - std::fmin(topLeftCoord.longitude, annotation.coordinate.longitude)); - bottomRightCoord = CLLocationCoordinate2DMake(std::fmin(bottomRightCoord.latitude, annotation.coordinate.latitude), - std::fmax(bottomRightCoord.longitude, annotation.coordinate.longitude)); - } - - MKCoordinateRegion region = MKCoordinateRegionMake(CLLocationCoordinate2DMake(topLeftCoord.latitude - (topLeftCoord.latitude - bottomRightCoord.latitude) * 0.5, - topLeftCoord.longitude + (bottomRightCoord.longitude - topLeftCoord.longitude) * 0.5), - MKCoordinateSpanMake(std::fabs(topLeftCoord.latitude - bottomRightCoord.latitude) * 2, - std::fabs(bottomRightCoord.longitude - topLeftCoord.longitude) * 2)); - - return region; -} - --(ASMapNodeShowAnnotationsOptions)showAnnotationsOptions { - return ASLockedSelf(_showAnnotationsOptions); -} - --(void)setShowAnnotationsOptions:(ASMapNodeShowAnnotationsOptions)showAnnotationsOptions { - ASLockScopeSelf(); - _showAnnotationsOptions = showAnnotationsOptions; -} - -#pragma mark - Layout -- (void)setSnapshotSizeWithReloadIfNeeded:(CGSize)snapshotSize -{ - if (snapshotSize.height > 0 && snapshotSize.width > 0 && !CGSizeEqualToSize(self.options.size, snapshotSize)) { - _options.size = snapshotSize; - if (_snapshotter) { - [self destroySnapshotter]; - [self takeSnapshot]; - } - } -} - -- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize -{ - // FIXME: Need a better way to allow maps to take up the right amount of space in a layout (sizeRange, etc) - // These fallbacks protect against inheriting a constrainedSize that contains a CGFLOAT_MAX value. - if (!ASIsCGSizeValidForLayout(constrainedSize)) { - //ASDisplayNodeAssert(NO, @"Invalid width or height in ASMapNode"); - constrainedSize = CGSizeZero; - } - [self setSnapshotSizeWithReloadIfNeeded:constrainedSize]; - return constrainedSize; -} - -- (void)calculatedLayoutDidChange -{ - [super calculatedLayoutDidChange]; - - if (_snapshotAfterLayout) { - [self takeSnapshot]; - } -} - -// -layout isn't usually needed over -layoutSpecThatFits, but this way we can avoid a needless node wrapper for MKMapView. -- (void)layout -{ - [super layout]; - if (self.isLiveMap) { - _mapView.frame = CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height); - } else { - // If our bounds.size is different from our current snapshot size, then let's request a new image from MKMapSnapshotter. - if (_needsMapReloadOnBoundsChange) { - [self setSnapshotSizeWithReloadIfNeeded:self.bounds.size]; - // FIXME: Adding a check for Preload here seems to cause intermittent map load failures, but shouldn't. - // if (ASInterfaceStateIncludesPreload(self.interfaceState)) { - } - } -} - -- (BOOL)supportsLayerBacking -{ - return NO; -} - -@end -<<<<<<< HEAD -#endif - -#endif -======= -#endif // TARGET_OS_IOS && AS_USE_MAPKIT ->>>>>>> 565da7d4935740d12fc204aa061faf093831da1e diff --git a/submodules/AsyncDisplayKit/Source/ASMultiplexImageNode.h b/submodules/AsyncDisplayKit/Source/ASMultiplexImageNode.h deleted file mode 100644 index 42342422a9..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASMultiplexImageNode.h +++ /dev/null @@ -1,271 +0,0 @@ -// -// ASMultiplexImageNode.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@protocol ASMultiplexImageNodeDelegate; -@protocol ASMultiplexImageNodeDataSource; - -typedef id ASImageIdentifier; - -AS_EXTERN NSString *const ASMultiplexImageNodeErrorDomain; - -/** - * ASMultiplexImageNode error codes. - */ -typedef NS_ENUM(NSUInteger, ASMultiplexImageNodeErrorCode) { - /** - * Indicates that the data source didn't provide a source for an image identifier. - */ - ASMultiplexImageNodeErrorCodeNoSourceForImage = 0, - - /** - * Indicates that the best image identifier changed before a download for a worse identifier began. - */ - ASMultiplexImageNodeErrorCodeBestImageIdentifierChanged, - - /** - * Indicates that the Photos framework returned no image and no error. - * This may happen if the image is in iCloud and the user did not specify `allowsNetworkAccess` - * in their image request. - */ - ASMultiplexImageNodeErrorCodePhotosImageManagerFailedWithoutError, - - /** - * Indicates that the image node could not retrieve the PHAsset for a given asset identifier. - * This typically means that the user has not given Photos framework permissions yet or the asset - * has been removed from the device. - */ - ASMultiplexImageNodeErrorCodePHAssetIsUnavailable -}; - - -/** - * @abstract ASMultiplexImageNode is an image node that can load and display multiple versions of an image. For - * example, it can display a low-resolution version of an image while the high-resolution version is loading. - * - * @discussion ASMultiplexImageNode begins loading images when its resource can either return a UIImage directly, or a URL the image node should load. - */ -@interface ASMultiplexImageNode : ASImageNode - -/** - * @abstract The designated initializer. - * @param cache The object that implements a cache of images for the image node. - * @param downloader The object that implements image downloading for the image node. - * @discussion If `cache` is nil, the receiver will not attempt to retrieve images from a cache before downloading them. - * @return An initialized ASMultiplexImageNode. - */ -- (instancetype)initWithCache:(nullable id)cache downloader:(nullable id)downloader NS_DESIGNATED_INITIALIZER; - -/** - * @abstract The delegate, which must conform to the protocol. - */ -@property (nonatomic, weak) id delegate; - -/** - * @abstract The data source, which must conform to the protocol. - * @discussion This value is required for ASMultiplexImageNode to load images. - */ -@property (nonatomic, weak) id dataSource; - -/** - * @abstract Whether the receiver should download more than just its highest-quality image. Defaults to NO. - * - * @discussion ASMultiplexImageNode immediately loads and displays the first image specified in (its - * highest-quality image). If that image is not immediately available or cached, the node can download and display - * lesser-quality images. Set `downloadsIntermediateImages` to YES to enable this behaviour. - */ -@property (nonatomic) BOOL downloadsIntermediateImages; - -/** - * @abstract An array of identifiers representing various versions of an image for ASMultiplexImageNode to display. - * - * @discussion An identifier can be any object that conforms to NSObject and NSCopying. The array should be in - * decreasing order of image quality -- that is, the first identifier in the array represents the best version. - * - * @see for more information on the image loading process. - */ -@property (nonatomic, copy) NSArray *imageIdentifiers; - -/** - * @abstract Notify the receiver SSAA that its data source has new UIImages or NSURLs available for . - * - * @discussion If a higher-quality image than is currently displayed is now available, it will be loaded. - */ -- (void)reloadImageIdentifierSources; - -/** - * @abstract The identifier for the last image that the receiver loaded, or nil. - * - * @discussion This value may differ from if the image hasn't yet been displayed. - */ -@property (nullable, nonatomic, readonly) ASImageIdentifier loadedImageIdentifier; - -/** - * @abstract The identifier for the image that the receiver is currently displaying, or nil. - */ -@property (nullable, nonatomic, readonly) ASImageIdentifier displayedImageIdentifier; - -/** - * @abstract If the downloader implements progressive image rendering and this value is YES progressive renders of the - * image will be displayed as the image downloads. Regardless of this properties value, progress renders will - * only occur when the node is visible. Defaults to YES. - */ -@property (nonatomic) BOOL shouldRenderProgressImages; - -/** - * @abstract The image manager that this image node should use when requesting images from the Photos framework. If this is `nil` (the default), then `PHImageManager.defaultManager` is used. - - * @see `+[NSURL URLWithAssetLocalIdentifier:targetSize:contentMode:options:]` below. - */ -@property (nullable, nonatomic) PHImageManager *imageManager API_AVAILABLE(ios(8.0), tvos(10.0)); -@end - - -#pragma mark - -/** - * The methods declared by the ASMultiplexImageNodeDelegate protocol allow the adopting delegate to respond to - * notifications such as began, progressed and finished downloading, updated and displayed an image. - */ -@protocol ASMultiplexImageNodeDelegate - -@optional -/** - * @abstract Notification that the image node began downloading an image. - * @param imageNode The sender. - * @param imageIdentifier The identifier for the image that is downloading. - */ -- (void)multiplexImageNode:(ASMultiplexImageNode *)imageNode didStartDownloadOfImageWithIdentifier:(id)imageIdentifier; - -/** - * @abstract Notification that the image node's download progressed. - * @param imageNode The sender. - * @param downloadProgress The progress of the download. Value is between 0.0 and 1.0. - * @param imageIdentifier The identifier for the image that is downloading. - */ -- (void)multiplexImageNode:(ASMultiplexImageNode *)imageNode - didUpdateDownloadProgress:(CGFloat)downloadProgress - forImageWithIdentifier:(ASImageIdentifier)imageIdentifier; - -/** - * @abstract Notification that the image node's download has finished. - * @param imageNode The sender. - * @param imageIdentifier The identifier for the image that finished downloading. - * @param error The error that occurred while downloading, if one occurred; nil otherwise. - */ -- (void)multiplexImageNode:(ASMultiplexImageNode *)imageNode -didFinishDownloadingImageWithIdentifier:(ASImageIdentifier)imageIdentifier - error:(nullable NSError *)error; - -/** - * @abstract Notification that the image node's image was updated. - * @param imageNode The sender. - * @param image The new image, ready for display. - * @param imageIdentifier The identifier for `image`. - * @param previousImage The old, previously-loaded image. - * @param previousImageIdentifier The identifier for `previousImage`. - * @note This method does not indicate that `image` has been displayed. - * @see <[ASMultiplexImageNodeDelegate multiplexImageNode:didDisplayUpdatedImage:withIdentifier:]>. - */ -- (void)multiplexImageNode:(ASMultiplexImageNode *)imageNode - didUpdateImage:(nullable UIImage *)image - withIdentifier:(nullable ASImageIdentifier)imageIdentifier - fromImage:(nullable UIImage *)previousImage - withIdentifier:(nullable ASImageIdentifier)previousImageIdentifier; - -/** - * @abstract Notification that the image node displayed a new image. - * @param imageNode The sender. - * @param image The new image, now being displayed. - * @param imageIdentifier The identifier for `image`. - * @discussion This method is only called when `image` changes, and not on subsequent redisplays of the same image. - */ -- (void)multiplexImageNode:(ASMultiplexImageNode *)imageNode - didDisplayUpdatedImage:(nullable UIImage *)image - withIdentifier:(nullable ASImageIdentifier)imageIdentifier; - -/** - * @abstract Notification that the image node finished displaying an image. - * @param imageNode The sender. - * @discussion This method is called every time an image is displayed, whether or not it has changed. - */ -- (void)multiplexImageNodeDidFinishDisplay:(ASMultiplexImageNode *)imageNode; - -@end - - -#pragma mark - -/** - * The ASMultiplexImageNodeDataSource protocol is adopted by an object that provides the multiplex image node, - * for each image identifier, an image or a URL the image node should load. - */ -@protocol ASMultiplexImageNodeDataSource - -@optional -/** - * @abstract An image for the specified identifier. - * @param imageNode The sender. - * @param imageIdentifier The identifier for the image that should be returned. - * @discussion If the image is already available to the data source, this method should be used in lieu of providing the - * URL to the image via -multiplexImageNode:URLForImageIdentifier:. - * @return A UIImage corresponding to `imageIdentifier`, or nil if none is available. - */ -- (nullable UIImage *)multiplexImageNode:(ASMultiplexImageNode *)imageNode imageForImageIdentifier:(ASImageIdentifier)imageIdentifier; - -/** - * @abstract An image URL for the specified identifier. - * @param imageNode The sender. - * @param imageIdentifier The identifier for the image that will be downloaded. - * @discussion Supported URLs include HTTP, HTTPS, AssetsLibrary, and FTP URLs as well as Photos framework URLs (see note). - * - * If the image is already available to the data source, it should be provided via <[ASMultiplexImageNodeDataSource - * multiplexImageNode:imageForImageIdentifier:]> instead. - * @return An NSURL for the image identified by `imageIdentifier`, or nil if none is available. - * @see `+[NSURL URLWithAssetLocalIdentifier:targetSize:contentMode:options:]` below. - */ -- (nullable NSURL *)multiplexImageNode:(ASMultiplexImageNode *)imageNode URLForImageIdentifier:(ASImageIdentifier)imageIdentifier; - -/** - * @abstract A PHAsset for the specific asset local identifier - * @param imageNode The sender. - * @param assetLocalIdentifier The local identifier for a PHAsset that this image node is loading. - * - * @discussion This optional method can improve image performance if your data source already has the PHAsset available. - * If this method is not implemented, or returns nil, the image node will request the asset from the Photos framework. - * @note This method may be called from any thread. - * @return A PHAsset corresponding to `assetLocalIdentifier`, or nil if none is available. - */ -- (nullable PHAsset *)multiplexImageNode:(ASMultiplexImageNode *)imageNode assetForLocalIdentifier:(NSString *)assetLocalIdentifier API_AVAILABLE(ios(8.0), tvos(10.0)); -@end - -#pragma mark - -@interface NSURL (ASPhotosFrameworkURLs) - -/** - * @abstract Create an NSURL that specifies an image from the Photos framework. - * - * @discussion When implementing `-multiplexImageNode:URLForImageIdentifier:`, you can return a URL - * created by this method and the image node will attempt to load the image from the Photos framework. - * @note The `synchronous` flag in `options` is ignored. - * @note The `Opportunistic` delivery mode is not supported and will be treated as `HighQualityFormat`. - */ -+ (NSURL *)URLWithAssetLocalIdentifier:(NSString *)assetLocalIdentifier - targetSize:(CGSize)targetSize - contentMode:(PHImageContentMode)contentMode - options:(PHImageRequestOptions *)options NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT API_AVAILABLE(ios(8.0), tvos(10.0)); - -@end - -NS_ASSUME_NONNULL_END -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASMultiplexImageNode.mm b/submodules/AsyncDisplayKit/Source/ASMultiplexImageNode.mm deleted file mode 100644 index 476ace1a38..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASMultiplexImageNode.mm +++ /dev/null @@ -1,962 +0,0 @@ -// -// ASMultiplexImageNode.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// -#ifndef MINIMAL_ASDK -#import - -#if TARGET_OS_IOS && AS_USE_ASSETS_LIBRARY -#import -#endif - -#import -#import -#import -#import -#import -#import -#import -#import - -#if AS_USE_PHOTOS -#import -#endif - -#if AS_PIN_REMOTE_IMAGE -#import -#else -#import -#endif - -using AS::MutexLocker; - -NSString *const ASMultiplexImageNodeErrorDomain = @"ASMultiplexImageNodeErrorDomain"; - -#if AS_USE_ASSETS_LIBRARY -static NSString *const kAssetsLibraryURLScheme = @"assets-library"; -#endif - -static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; - -/** - @abstract Signature for the block to be performed after an image has loaded. - @param image The image that was loaded, or nil if no image was loaded. - @param imageIdentifier The identifier of the image that was loaded, or nil if no image was loaded. - @param error An error describing why an image couldn't be loaded, if it failed to load; nil otherwise. - */ -typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdentifier, NSError *error); - -@interface ASMultiplexImageNode () -{ -@private - // Core. - id _cache; - - id _downloader; - struct { - unsigned int downloaderImplementsSetProgress:1; - unsigned int downloaderImplementsSetPriority:1; - unsigned int downloaderImplementsDownloadWithPriority:1; - } _downloaderFlags; - - __weak id _delegate; - struct { - unsigned int downloadStart:1; - unsigned int downloadProgress:1; - unsigned int downloadFinish:1; - unsigned int updatedImageDisplayFinish:1; - unsigned int updatedImage:1; - unsigned int displayFinish:1; - } _delegateFlags; - - __weak id _dataSource; - struct { - unsigned int image:1; - unsigned int URL:1; - unsigned int asset:1; - } _dataSourceFlags; - - // Image flags. - BOOL _downloadsIntermediateImages; // Defaults to NO. - AS::Mutex _imageIdentifiersLock; - NSArray *_imageIdentifiers; - id _loadedImageIdentifier; - id _loadingImageIdentifier; - id _displayedImageIdentifier; - __weak NSOperation *_phImageRequestOperation; - - // Networking. - AS::RecursiveMutex _downloadIdentifierLock; - id _downloadIdentifier; - - // Properties - BOOL _shouldRenderProgressImages; - - //set on init only - BOOL _cacheSupportsClearing; -} - -//! @abstract Read-write redeclaration of property declared in ASMultiplexImageNode.h. -@property (nonatomic, copy) id loadedImageIdentifier; - -//! @abstract The image identifier that's being loaded by _loadNextImageWithCompletion:. -@property (nonatomic, copy) id loadingImageIdentifier; - -/** - @abstract Returns the next image identifier that should be downloaded. - @discussion This method obeys and reflects the value of `downloadsIntermediateImages`. - @result The next image identifier, from `_imageIdentifiers`, that should be downloaded, or nil if no image should be downloaded next. - */ -- (id)_nextImageIdentifierToDownload; - -/** - @abstract Returns the best image that is immediately available from our datasource without downloading or hitting the cache. - @param imageIdentifierOut Upon return, the image identifier for the returned image; nil otherwise. - @discussion This method exclusively uses the data source's -multiplexImageNode:imageForIdentifier: method to return images. It does not fetch from the cache or kick off downloading. - @result The best UIImage available immediately; nil if no image is immediately available. - */ -- (UIImage *)_bestImmediatelyAvailableImageFromDataSource:(id *)imageIdentifierOut; - -/** - @abstract Loads and displays the next image in the receiver's loading sequence. - @discussion This method obeys `downloadsIntermediateImages`. This method has no effect if nothing further should be loaded, as indicated by `_nextImageIdentifierToDownload`. This method will load the next image from the data-source, if possible; otherwise, the session's image cache will be queried for the desired image, and as a last resort, the image will be downloaded. - */ -- (void)_loadNextImage; - -/** - @abstract Fetches the image corresponding to the given imageIdentifier from the given URL from the session's image cache. - @param imageIdentifier The identifier for the image to be fetched. May not be nil. - @param imageURL The URL of the image to fetch. May not be nil. - @param completionBlock The block to be performed when the image has been fetched from the cache, if possible. May not be nil. - @discussion This method queries both the session's in-memory and on-disk caches (with preference for the in-memory cache). - */ -- (void)_fetchImageWithIdentifierFromCache:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image))completionBlock; - -#if TARGET_OS_IOS && AS_USE_ASSETS_LIBRARY -/** - @abstract Loads the image corresponding to the given assetURL from the device's Assets Library. - @param imageIdentifier The identifier for the image to be loaded. May not be nil. - @param assetURL The assets-library URL (e.g., "assets-library://identifier") of the image to load, from ALAsset. May not be nil. - @param completionBlock The block to be performed when the image has been loaded, if possible. May not be nil. - */ -- (void)_loadALAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL completion:(void (^)(UIImage *image, NSError *error))completionBlock; -#endif - -#if AS_USE_PHOTOS -/** - @abstract Loads the image corresponding to the given image request from the Photos framework. - @param imageIdentifier The identifier for the image to be loaded. May not be nil. - @param request The photos image request to load. May not be nil. - @param completionBlock The block to be performed when the image has been loaded, if possible. May not be nil. - */ -- (void)_loadPHAssetWithRequest:(ASPhotosFrameworkImageRequest *)request identifier:(id)imageIdentifier completion:(void (^)(UIImage *image, NSError *error))completionBlock API_AVAILABLE(ios(8.0), tvos(10.0)); -#endif - -/** - @abstract Downloads the image corresponding to the given imageIdentifier from the given URL. - @param imageIdentifier The identifier for the image to be downloaded. May not be nil. - @param imageURL The URL of the image to downloaded. May not be nil. - @param completionBlock The block to be performed when the image has been downloaded, if possible. May not be nil. - */ -- (void)_downloadImageWithIdentifier:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image, NSError *error))completionBlock; - -@end - -@implementation ASMultiplexImageNode - -#pragma mark - Getting Started / Tearing Down -- (instancetype)initWithCache:(id)cache downloader:(id)downloader -{ - if (!(self = [super init])) - return nil; - - _cache = (id)cache; - _downloader = (id)downloader; - - _downloaderFlags.downloaderImplementsSetProgress = [downloader respondsToSelector:@selector(setProgressImageBlock:callbackQueue:withDownloadIdentifier:)]; - _downloaderFlags.downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)]; - _downloaderFlags.downloaderImplementsDownloadWithPriority = [downloader respondsToSelector:@selector(downloadImageWithURL:priority:callbackQueue:downloadProgress:completion:)]; - - _cacheSupportsClearing = [cache respondsToSelector:@selector(clearFetchedImageFromCacheWithURL:)]; - - _shouldRenderProgressImages = YES; - - self.shouldBypassEnsureDisplay = YES; - - return self; -} - -- (instancetype)init -{ -#if AS_PIN_REMOTE_IMAGE - return [self initWithCache:[ASPINRemoteImageDownloader sharedDownloader] downloader:[ASPINRemoteImageDownloader sharedDownloader]]; -#else - return [self initWithCache:nil downloader:[ASBasicImageDownloader sharedImageDownloader]]; -#endif -} - -- (void)dealloc -{ - [_phImageRequestOperation cancel]; -} - -#pragma mark - ASDisplayNode Overrides - -- (void)clearContents -{ - [super clearContents]; // This actually clears the contents, so we need to do this first for our displayedImageIdentifier to be meaningful. - [self _setDisplayedImageIdentifier:nil withImage:nil]; - - // NOTE: We intentionally do not cancel image downloads until `clearPreloadedData`. -} - -- (void)didExitPreloadState -{ - [super didExitPreloadState]; - - [_phImageRequestOperation cancel]; - - [self _setDownloadIdentifier:nil]; - - if (_cacheSupportsClearing && self.loadedImageIdentifier != nil) { - NSURL *URL = [_dataSource multiplexImageNode:self URLForImageIdentifier:self.loadedImageIdentifier]; - if (URL != nil) { - [_cache clearFetchedImageFromCacheWithURL:URL]; - } - } - - // setting this to nil makes the node fetch images the next time its display starts - _loadedImageIdentifier = nil; - [self _setImage:nil]; -} - -- (void)didEnterPreloadState -{ - [super didEnterPreloadState]; - - [self _loadImageIdentifiers]; -} - -- (void)displayDidFinish -{ - [super displayDidFinish]; - - // We may now be displaying the loaded identifier, if they're different. - UIImage *displayedImage = self.image; - if (displayedImage) { - if (!ASObjectIsEqual(_displayedImageIdentifier, _loadedImageIdentifier)) - [self _setDisplayedImageIdentifier:_loadedImageIdentifier withImage:displayedImage]; - - // Delegateify - if (_delegateFlags.displayFinish) { - if (ASDisplayNodeThreadIsMain()) - [_delegate multiplexImageNodeDidFinishDisplay:self]; - else { - __weak __typeof__(self) weakSelf = self; - dispatch_async(dispatch_get_main_queue(), ^{ - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) - return; - [strongSelf.delegate multiplexImageNodeDidFinishDisplay:strongSelf]; - }); - } - } - } -} - -- (BOOL)placeholderShouldPersist -{ - return (self.image == nil && self.animatedImage == nil && self.imageIdentifiers.count > 0); -} - -/* displayWillStartAsynchronously in ASNetworkImageNode has a very similar implementation. Changes here are likely necessary - in ASNetworkImageNode as well. */ -- (void)displayWillStartAsynchronously:(BOOL)asynchronously -{ - [super displayWillStartAsynchronously:asynchronously]; - [self didEnterPreloadState]; - [self _updatePriorityOnDownloaderIfNeededWithDefaultPriority:ASImageDownloaderPriorityImminent]; -} - -/* didEnterVisibleState / didExitVisibleState in ASNetworkImageNode has a very similar implementation. Changes here are likely necessary - in ASNetworkImageNode as well. */ -- (void)didEnterVisibleState -{ - [super didEnterVisibleState]; - [self _updatePriorityOnDownloaderIfNeededWithDefaultPriority:ASImageDownloaderPriorityVisible]; - [self _updateProgressImageBlockOnDownloaderIfNeeded]; -} - -- (void)didExitVisibleState -{ - [super didExitVisibleState]; - [self _updatePriorityOnDownloaderIfNeededWithDefaultPriority:ASImageDownloaderPriorityPreload]; - [self _updateProgressImageBlockOnDownloaderIfNeeded]; -} - -- (void)didExitDisplayState -{ - [super didExitDisplayState]; - if (ASActivateExperimentalFeature(ASExperimentalImageDownloaderPriority)) { - [self _updatePriorityOnDownloaderIfNeededWithDefaultPriority:ASImageDownloaderPriorityPreload]; - } -} - -#pragma mark - Core - -- (void)setImage:(UIImage *)image -{ - ASDisplayNodeAssert(NO, @"Setting the image directly on an ASMultiplexImageNode is unsafe. It will be cleared in didExitPreloadRange and will have no way to restore in didEnterPreloadRange"); - super.image = image; -} - -- (void)_setImage:(UIImage *)image -{ - super.image = image; -} - -- (void)setDelegate:(id )delegate -{ - if (_delegate == delegate) - return; - - _delegate = delegate; - _delegateFlags.downloadStart = [_delegate respondsToSelector:@selector(multiplexImageNode:didStartDownloadOfImageWithIdentifier:)]; - _delegateFlags.downloadProgress = [_delegate respondsToSelector:@selector(multiplexImageNode:didUpdateDownloadProgress:forImageWithIdentifier:)]; - _delegateFlags.downloadFinish = [_delegate respondsToSelector:@selector(multiplexImageNode:didFinishDownloadingImageWithIdentifier:error:)]; - _delegateFlags.updatedImageDisplayFinish = [_delegate respondsToSelector:@selector(multiplexImageNode:didDisplayUpdatedImage:withIdentifier:)]; - _delegateFlags.updatedImage = [_delegate respondsToSelector:@selector(multiplexImageNode:didUpdateImage:withIdentifier:fromImage:withIdentifier:)]; - _delegateFlags.displayFinish = [_delegate respondsToSelector:@selector(multiplexImageNodeDidFinishDisplay:)]; -} - - -- (void)setDataSource:(id )dataSource -{ - if (_dataSource == dataSource) - return; - - _dataSource = dataSource; - _dataSourceFlags.image = [_dataSource respondsToSelector:@selector(multiplexImageNode:imageForImageIdentifier:)]; - _dataSourceFlags.URL = [_dataSource respondsToSelector:@selector(multiplexImageNode:URLForImageIdentifier:)]; - if (AS_AVAILABLE_IOS_TVOS(9, 10)) { - _dataSourceFlags.asset = [_dataSource respondsToSelector:@selector(multiplexImageNode:assetForLocalIdentifier:)]; - } -} - - -- (void)setShouldRenderProgressImages:(BOOL)shouldRenderProgressImages -{ - [self lock]; - if (shouldRenderProgressImages == _shouldRenderProgressImages) { - [self unlock]; - return; - } - - _shouldRenderProgressImages = shouldRenderProgressImages; - - [self unlock]; - [self _updateProgressImageBlockOnDownloaderIfNeeded]; -} - -- (BOOL)shouldRenderProgressImages -{ - return ASLockedSelf(_shouldRenderProgressImages); -} - -#pragma mark - - -#pragma mark - - -- (NSArray *)imageIdentifiers -{ - MutexLocker l(_imageIdentifiersLock); - return _imageIdentifiers; -} - -- (void)setImageIdentifiers:(NSArray *)imageIdentifiers -{ - { - MutexLocker l(_imageIdentifiersLock); - if (ASObjectIsEqual(_imageIdentifiers, imageIdentifiers)) { - return; - } - - _imageIdentifiers = [[NSArray alloc] initWithArray:imageIdentifiers copyItems:YES]; - } - - [self setNeedsPreload]; -} - -- (void)reloadImageIdentifierSources -{ - // setting this to nil makes the node think it has not downloaded any images - _loadedImageIdentifier = nil; - [self _loadImageIdentifiers]; -} - -#pragma mark - - - -#pragma mark - Core Internal -- (void)_setDisplayedImageIdentifier:(id)displayedImageIdentifier withImage:(UIImage *)image -{ - ASDisplayNodeAssertMainThread(); - - if (ASObjectIsEqual(_displayedImageIdentifier, displayedImageIdentifier)) { - return; - } - - _displayedImageIdentifier = displayedImageIdentifier; - - // Delegateify. - // Note that we're using the params here instead of self.image and _displayedImageIdentifier because those can change before the async block below executes. - if (_delegateFlags.updatedImageDisplayFinish) { - if (ASDisplayNodeThreadIsMain()) - [_delegate multiplexImageNode:self didDisplayUpdatedImage:image withIdentifier:displayedImageIdentifier]; - else { - __weak __typeof__(self) weakSelf = self; - dispatch_async(dispatch_get_main_queue(), ^{ - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) - return; - [strongSelf.delegate multiplexImageNode:strongSelf didDisplayUpdatedImage:image withIdentifier:displayedImageIdentifier]; - }); - } - } -} - -- (void)_setDownloadIdentifier:(id)downloadIdentifier -{ - MutexLocker l(_downloadIdentifierLock); - if (ASObjectIsEqual(downloadIdentifier, _downloadIdentifier)) - return; - - if (_downloadIdentifier) { - [_downloader cancelImageDownloadForIdentifier:_downloadIdentifier]; - } - _downloadIdentifier = downloadIdentifier; -} - -#pragma mark - Image Loading Machinery - -- (void)_loadImageIdentifiers -{ - // Grab the best possible image we can load right now. - id bestImmediatelyAvailableImageIdentifier = nil; - UIImage *bestImmediatelyAvailableImage = [self _bestImmediatelyAvailableImageFromDataSource:&bestImmediatelyAvailableImageIdentifier]; - as_log_verbose(ASImageLoadingLog(), "%@ Best immediately available image identifier is %@", self, bestImmediatelyAvailableImageIdentifier); - - // Load it. This kicks off cache fetching/downloading, as appropriate. - [self _finishedLoadingImage:bestImmediatelyAvailableImage forIdentifier:bestImmediatelyAvailableImageIdentifier error:nil]; -} - -- (UIImage *)_bestImmediatelyAvailableImageFromDataSource:(id *)imageIdentifierOut -{ - MutexLocker l(_imageIdentifiersLock); - - // If we don't have any identifiers to load or don't implement the image DS method, bail. - if ([_imageIdentifiers count] == 0 || !_dataSourceFlags.image) { - return nil; - } - - // Grab the best available image from the data source. - UIImage *existingImage = self.image; - for (id imageIdentifier in _imageIdentifiers) { - // If this image is already loaded, don't request it from the data source again because - // the data source may generate a new instance of UIImage that returns NO for isEqual: - // and we'll end up in an infinite loading loop. - UIImage *image = ASObjectIsEqual(imageIdentifier, _loadedImageIdentifier) ? existingImage : [_dataSource multiplexImageNode:self imageForImageIdentifier:imageIdentifier]; - if (image) { - if (imageIdentifierOut) { - *imageIdentifierOut = imageIdentifier; - } - - return image; - } - } - - return nil; -} - -#pragma mark - - -- (void)_updatePriorityOnDownloaderIfNeededWithDefaultPriority:(ASImageDownloaderPriority)defaultPriority -{ - ASAssertUnlocked(_downloadIdentifierLock); - - if (_downloaderFlags.downloaderImplementsSetPriority) { - // Read our interface state before locking so that we don't lock super while holding our lock. - ASInterfaceState interfaceState = self.interfaceState; - MutexLocker l(_downloadIdentifierLock); - - if (_downloadIdentifier != nil) { - ASImageDownloaderPriority priority = defaultPriority; - if (ASActivateExperimentalFeature(ASExperimentalImageDownloaderPriority)) { - priority = ASImageDownloaderPriorityWithInterfaceState(interfaceState); - } - - [_downloader setPriority:priority withDownloadIdentifier:_downloadIdentifier]; - } - } -} - -- (void)_updateProgressImageBlockOnDownloaderIfNeeded -{ - ASAssertUnlocked(_downloadIdentifierLock); - - BOOL shouldRenderProgressImages = self.shouldRenderProgressImages; - - // Read our interface state before locking so that we don't lock super while holding our lock. - ASInterfaceState interfaceState = self.interfaceState; - MutexLocker l(_downloadIdentifierLock); - - if (!_downloaderFlags.downloaderImplementsSetProgress || _downloadIdentifier == nil) { - return; - } - - ASImageDownloaderProgressImage progress = nil; - if (shouldRenderProgressImages && ASInterfaceStateIncludesVisible(interfaceState)) { - __weak __typeof__(self) weakSelf = self; - progress = ^(UIImage * _Nonnull progressImage, CGFloat progress, id _Nullable downloadIdentifier) { - __typeof__(self) strongSelf = weakSelf; - if (strongSelf == nil) { - return; - } - - MutexLocker l(strongSelf->_downloadIdentifierLock); - //Getting a result back for a different download identifier, download must not have been successfully canceled - if (ASObjectIsEqual(strongSelf->_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { - return; - } - [strongSelf _setImage:progressImage]; - }; - } - [_downloader setProgressImageBlock:progress callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:_downloadIdentifier]; -} - -- (void)_clearImage -{ - // Destruction of bigger images on the main thread can be expensive - // and can take some time, so we dispatch onto a bg queue to - // actually dealloc. - UIImage *image = self.image; - CGSize imageSize = image.size; - BOOL shouldReleaseImageOnBackgroundThread = imageSize.width > kMinReleaseImageOnBackgroundSize.width || - imageSize.height > kMinReleaseImageOnBackgroundSize.height; - [self _setImage:nil]; - if (shouldReleaseImageOnBackgroundThread) { - ASPerformBackgroundDeallocation(&image); - } -} - -#pragma mark - -- (id)_nextImageIdentifierToDownload -{ - MutexLocker l(_imageIdentifiersLock); - - // If we've already loaded the best identifier, we've got nothing else to do. - id bestImageIdentifier = _imageIdentifiers.firstObject; - if (!bestImageIdentifier || ASObjectIsEqual(_loadedImageIdentifier, bestImageIdentifier)) { - return nil; - } - - id nextImageIdentifierToDownload = nil; - - // If we're not supposed to download intermediate images, load the best identifier we've got. - if (!_downloadsIntermediateImages) { - nextImageIdentifierToDownload = bestImageIdentifier; - } - // Otherwise, load progressively. - else { - NSUInteger loadedIndex = [_imageIdentifiers indexOfObject:_loadedImageIdentifier]; - - // If nothing has loaded yet, load the worst identifier. - if (loadedIndex == NSNotFound) { - nextImageIdentifierToDownload = [_imageIdentifiers lastObject]; - } - // Otherwise, load the next best identifier (if there is one) - else if (loadedIndex > 0) { - nextImageIdentifierToDownload = _imageIdentifiers[loadedIndex - 1]; - } - } - - return nextImageIdentifierToDownload; -} - -- (void)_loadNextImage -{ - // Determine the next identifier to load (if any). - id nextImageIdentifier = [self _nextImageIdentifierToDownload]; - if (!nextImageIdentifier) { - [self _finishedLoadingImage:nil forIdentifier:nil error:nil]; - return; - } - - as_activity_create_for_scope("Load next image for multiplex image node"); - as_log_verbose(ASImageLoadingLog(), "Loading image for %@ ident: %@", self, nextImageIdentifier); - self.loadingImageIdentifier = nextImageIdentifier; - - __weak __typeof__(self) weakSelf = self; - ASMultiplexImageLoadCompletionBlock finishedLoadingBlock = ^(UIImage *image, id imageIdentifier, NSError *error) { - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) - return; - - // Only nil out the loading identifier if the loading identifier hasn't changed. - if (ASObjectIsEqual(strongSelf.loadingImageIdentifier, nextImageIdentifier)) { - strongSelf.loadingImageIdentifier = nil; - } - [strongSelf _finishedLoadingImage:image forIdentifier:imageIdentifier error:error]; - }; - - // Ask our data-source if it's got this image. - if (_dataSourceFlags.image) { - UIImage *image = [_dataSource multiplexImageNode:self imageForImageIdentifier:nextImageIdentifier]; - if (image) { - as_log_verbose(ASImageLoadingLog(), "Acquired image from data source for %@ ident: %@", self, nextImageIdentifier); - finishedLoadingBlock(image, nextImageIdentifier, nil); - return; - } - } - - NSURL *nextImageURL = (_dataSourceFlags.URL) ? [_dataSource multiplexImageNode:self URLForImageIdentifier:nextImageIdentifier] : nil; - // If we fail to get a URL for the image, we have no source and can't proceed. - if (!nextImageURL) { - as_log_error(ASImageLoadingLog(), "Could not acquire URL %@ ident: (%@)", self, nextImageIdentifier); - finishedLoadingBlock(nil, nil, [NSError errorWithDomain:ASMultiplexImageNodeErrorDomain code:ASMultiplexImageNodeErrorCodeNoSourceForImage userInfo:nil]); - return; - } - -#if TARGET_OS_IOS && AS_USE_ASSETS_LIBRARY - // If it's an assets-library URL, we need to fetch it from the assets library. - if ([[nextImageURL scheme] isEqualToString:kAssetsLibraryURLScheme]) { - // Load the asset. - [self _loadALAssetWithIdentifier:nextImageIdentifier URL:nextImageURL completion:^(UIImage *downloadedImage, NSError *error) { - as_log_verbose(ASImageLoadingLog(), "Acquired image from assets library for %@ %@", weakSelf, nextImageIdentifier); - finishedLoadingBlock(downloadedImage, nextImageIdentifier, error); - }]; - - return; - } -#endif - -#if AS_USE_PHOTOS - if (AS_AVAILABLE_IOS_TVOS(9, 10)) { - // Likewise, if it's a Photos asset, we need to fetch it accordingly. - if (ASPhotosFrameworkImageRequest *request = [ASPhotosFrameworkImageRequest requestWithURL:nextImageURL]) { - [self _loadPHAssetWithRequest:request identifier:nextImageIdentifier completion:^(UIImage *image, NSError *error) { - as_log_verbose(ASImageLoadingLog(), "Acquired image from Photos for %@ %@", weakSelf, nextImageIdentifier); - finishedLoadingBlock(image, nextImageIdentifier, error); - }]; - - return; - } - } -#endif - - // Otherwise, it's a web URL that we can download. - // First, check the cache. - [self _fetchImageWithIdentifierFromCache:nextImageIdentifier URL:nextImageURL completion:^(UIImage *imageFromCache) { - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) - return; - - // If we had a cache-hit, we're done. - if (imageFromCache) { - as_log_verbose(ASImageLoadingLog(), "Acquired image from cache for %@ id: %@ img: %@", strongSelf, nextImageIdentifier, imageFromCache); - finishedLoadingBlock(imageFromCache, nextImageIdentifier, nil); - return; - } - - // If the next image to load has changed, bail. - if (!ASObjectIsEqual([strongSelf _nextImageIdentifierToDownload], nextImageIdentifier)) { - finishedLoadingBlock(nil, nil, [NSError errorWithDomain:ASMultiplexImageNodeErrorDomain code:ASMultiplexImageNodeErrorCodeBestImageIdentifierChanged userInfo:nil]); - return; - } - - // Otherwise, we've got to download it. - [strongSelf _downloadImageWithIdentifier:nextImageIdentifier URL:nextImageURL completion:^(UIImage *downloadedImage, NSError *error) { - __typeof__(self) strongSelf = weakSelf; - if (downloadedImage) { - as_log_verbose(ASImageLoadingLog(), "Acquired image from download for %@ id: %@ img: %@", strongSelf, nextImageIdentifier, downloadedImage); - } else { - as_log_error(ASImageLoadingLog(), "Error downloading image for %@ id: %@ err: %@", strongSelf, nextImageIdentifier, error); - } - finishedLoadingBlock(downloadedImage, nextImageIdentifier, error); - }]; - }]; -} -#if TARGET_OS_IOS && AS_USE_ASSETS_LIBRARY -- (void)_loadALAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL completion:(void (^)(UIImage *image, NSError *error))completionBlock -{ - ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required"); - ASDisplayNodeAssertNotNil(assetURL, @"assetURL is required"); - ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required"); - - // ALAssetsLibrary was replaced in iOS 8 and deprecated in iOS 9. - // We'll drop support very soon. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - ALAssetsLibrary *assetLibrary = [[ALAssetsLibrary alloc] init]; - - [assetLibrary assetForURL:assetURL resultBlock:^(ALAsset *asset) { - ALAssetRepresentation *representation = [asset defaultRepresentation]; - CGImageRef coreGraphicsImage = [representation fullScreenImage]; - - UIImage *downloadedImage = (coreGraphicsImage ? [UIImage imageWithCGImage:coreGraphicsImage] : nil); - completionBlock(downloadedImage, nil); - } failureBlock:^(NSError *error) { - completionBlock(nil, error); - }]; -#pragma clang diagnostic pop -} -#endif - -#if AS_USE_PHOTOS -- (void)_loadPHAssetWithRequest:(ASPhotosFrameworkImageRequest *)request identifier:(id)imageIdentifier completion:(void (^)(UIImage *image, NSError *error))completionBlock -{ - ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required"); - ASDisplayNodeAssertNotNil(request, @"request is required"); - ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required"); - - /* - * Locking rationale: - * As of iOS 9, Photos.framework will eventually deadlock if you hit it with concurrent fetch requests. rdar://22984886 - * Concurrent image requests are OK, but metadata requests aren't, so we limit ourselves to one at a time. - */ - static NSLock *phRequestLock; - static NSOperationQueue *phImageRequestQueue; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - phRequestLock = [NSLock new]; - phImageRequestQueue = [NSOperationQueue new]; - phImageRequestQueue.maxConcurrentOperationCount = 10; - phImageRequestQueue.name = @"org.AsyncDisplayKit.MultiplexImageNode.phImageRequestQueue"; - }); - - // Each ASMultiplexImageNode can have max 1 inflight Photos image request operation - [_phImageRequestOperation cancel]; - - __weak __typeof(self) weakSelf = self; - NSOperation *newImageRequestOp = [NSBlockOperation blockOperationWithBlock:^{ - __strong __typeof(weakSelf) strongSelf = weakSelf; - if (strongSelf == nil) { return; } - - PHAsset *imageAsset = nil; - - // Try to get the asset immediately from the data source. - if (_dataSourceFlags.asset) { - imageAsset = [strongSelf.dataSource multiplexImageNode:strongSelf assetForLocalIdentifier:request.assetIdentifier]; - } - - // Fall back to locking and getting the PHAsset. - if (imageAsset == nil) { - [phRequestLock lock]; - // -[PHFetchResult dealloc] plays a role in the deadlock mentioned above, so we make sure the PHFetchResult is deallocated inside the critical section - @autoreleasepool { - imageAsset = [PHAsset fetchAssetsWithLocalIdentifiers:@[request.assetIdentifier] options:nil].firstObject; - } - [phRequestLock unlock]; - } - - if (imageAsset == nil) { - NSError *error = [NSError errorWithDomain:ASMultiplexImageNodeErrorDomain code:ASMultiplexImageNodeErrorCodePHAssetIsUnavailable userInfo:nil]; - completionBlock(nil, error); - return; - } - - PHImageRequestOptions *options = [request.options copy]; - - // We don't support opportunistic delivery – one request, one image. - if (options.deliveryMode == PHImageRequestOptionsDeliveryModeOpportunistic) { - options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat; - } - - if (options.deliveryMode == PHImageRequestOptionsDeliveryModeHighQualityFormat) { - // Without this flag the result will be delivered on the main queue, which is pointless - // But synchronous -> HighQualityFormat so we only use it if high quality format is specified - options.synchronous = YES; - } - - PHImageManager *imageManager = strongSelf.imageManager ? : PHImageManager.defaultManager; - [imageManager requestImageForAsset:imageAsset targetSize:request.targetSize contentMode:request.contentMode options:options resultHandler:^(UIImage *image, NSDictionary *info) { - NSError *error = info[PHImageErrorKey]; - - if (error == nil && image == nil) { - error = [NSError errorWithDomain:ASMultiplexImageNodeErrorDomain code:ASMultiplexImageNodeErrorCodePhotosImageManagerFailedWithoutError userInfo:nil]; - } - - if (NSThread.isMainThread) { - dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{ - completionBlock(image, error); - }); - } else { - completionBlock(image, error); - } - }]; - }]; - // If you don't set this, iOS will sometimes infer NSQualityOfServiceUserInteractive and promote the entire queue to that level, damaging system responsiveness - newImageRequestOp.qualityOfService = NSQualityOfServiceUserInitiated; - _phImageRequestOperation = newImageRequestOp; - [phImageRequestQueue addOperation:newImageRequestOp]; -} -#endif - -- (void)_fetchImageWithIdentifierFromCache:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image))completionBlock -{ - ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required"); - ASDisplayNodeAssertNotNil(imageURL, @"imageURL is required"); - ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required"); - - if (_cache) { - [_cache cachedImageWithURL:imageURL callbackQueue:dispatch_get_main_queue() completion:^(id imageContainer) { - completionBlock([imageContainer asdk_image]); - }]; - } - // If we don't have a cache, just fail immediately. - else { - completionBlock(nil); - } -} - -- (void)_downloadImageWithIdentifier:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image, NSError *error))completionBlock -{ - ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required"); - ASDisplayNodeAssertNotNil(imageURL, @"imageURL is required"); - ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required"); - - // Delegate (start) - if (_delegateFlags.downloadStart) - [_delegate multiplexImageNode:self didStartDownloadOfImageWithIdentifier:imageIdentifier]; - - __weak __typeof__(self) weakSelf = self; - ASImageDownloaderProgress downloadProgressBlock = NULL; - if (_delegateFlags.downloadProgress) { - downloadProgressBlock = ^(CGFloat progress) { - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) - return; - [strongSelf.delegate multiplexImageNode:strongSelf didUpdateDownloadProgress:progress forImageWithIdentifier:imageIdentifier]; - }; - } - - ASImageDownloaderCompletion completion = ^(id imageContainer, NSError *error, id downloadIdentifier, id userInfo) { - // We dereference iVars directly, so we can't have weakSelf going nil on us. - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) - return; - - MutexLocker l(strongSelf->_downloadIdentifierLock); - //Getting a result back for a different download identifier, download must not have been successfully canceled - if (ASObjectIsEqual(strongSelf->_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { - return; - } - - completionBlock([imageContainer asdk_image], error); - - // Delegateify. - if (strongSelf->_delegateFlags.downloadFinish) - [strongSelf->_delegate multiplexImageNode:weakSelf didFinishDownloadingImageWithIdentifier:imageIdentifier error:error]; - }; - - // Download! - ASPerformBlockOnBackgroundThread(^{ - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) - return; - - dispatch_queue_t callbackQueue = dispatch_get_main_queue(); - - id downloadIdentifier; - if (strongSelf->_downloaderFlags.downloaderImplementsDownloadWithPriority - && ASActivateExperimentalFeature(ASExperimentalImageDownloaderPriority)) { - - /* - Decide a priority based on the current interface state of this node. - It can happen that this method was called when the node entered preload state - but the interface state, at this point, tells us that the node is (going to be) visible, - If that's the case, we jump to a higher priority directly. - */ - ASImageDownloaderPriority priority = ASImageDownloaderPriorityWithInterfaceState(strongSelf.interfaceState); - downloadIdentifier = [strongSelf->_downloader downloadImageWithURL:imageURL - priority:priority - callbackQueue:callbackQueue - downloadProgress:downloadProgressBlock - completion:completion]; - } else { - /* - Kick off a download with default priority. - The actual "default" value is decided by the downloader. - ASBasicImageDownloader and ASPINRemoteImageDownloader both use ASImageDownloaderPriorityImminent - which is mapped to NSURLSessionTaskPriorityDefault. - - This means that preload and display nodes use the same priority - and their requests are put into the same pool. - */ - downloadIdentifier = [strongSelf->_downloader downloadImageWithURL:imageURL - callbackQueue:callbackQueue - downloadProgress:downloadProgressBlock - completion:completion]; - } - - [strongSelf _setDownloadIdentifier:downloadIdentifier]; - [strongSelf _updateProgressImageBlockOnDownloaderIfNeeded]; - }); -} - -#pragma mark - -- (void)_finishedLoadingImage:(UIImage *)image forIdentifier:(id)imageIdentifier error:(NSError *)error -{ - // If we failed to load, we stop the loading process. - // Note that if we bailed before we began downloading because the best identifier changed, we don't bail, but rather just begin loading the best image identifier. - if (error && !([error.domain isEqual:ASMultiplexImageNodeErrorDomain] && error.code == ASMultiplexImageNodeErrorCodeBestImageIdentifierChanged)) - return; - - - _imageIdentifiersLock.lock(); - NSUInteger imageIdentifierCount = [_imageIdentifiers count]; - _imageIdentifiersLock.unlock(); - - // Update our image if we got one, or if we're not supposed to display one at all. - // We explicitly perform this check because our datasource often doesn't give back immediately available images, even though we might have downloaded one already. - // Because we seed this call with bestImmediatelyAvailableImageFromDataSource, we must be careful not to trample an existing image. - if (image || imageIdentifierCount == 0) { - as_log_verbose(ASImageLoadingLog(), "[%p] loaded -> displaying (%@, %@)", self, imageIdentifier, image); - id previousIdentifier = self.loadedImageIdentifier; - UIImage *previousImage = self.image; - - self.loadedImageIdentifier = imageIdentifier; - [self _setImage:image]; - - if (_delegateFlags.updatedImage) { - [_delegate multiplexImageNode:self didUpdateImage:image withIdentifier:imageIdentifier fromImage:previousImage withIdentifier:previousIdentifier]; - } - - } - - // Load our next image, if we have one to load. - if ([self _nextImageIdentifierToDownload]) - [self _loadNextImage]; -} - -@end - -#if AS_USE_PHOTOS -@implementation NSURL (ASPhotosFrameworkURLs) - -+ (NSURL *)URLWithAssetLocalIdentifier:(NSString *)assetLocalIdentifier targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(PHImageRequestOptions *)options NS_RETURNS_RETAINED -{ - ASPhotosFrameworkImageRequest *request = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:assetLocalIdentifier]; - request.options = options; - request.contentMode = contentMode; - request.targetSize = targetSize; - return request.url; -} - -@end -#endif -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASNavigationController.h b/submodules/AsyncDisplayKit/Source/ASNavigationController.h deleted file mode 100644 index 4f7ac12df4..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASNavigationController.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// ASNavigationController.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * ASNavigationController - * - * @discussion ASNavigationController is a drop in replacement for UINavigationController - * which improves memory efficiency by implementing the @c ASManagesChildVisibilityDepth protocol. - * You can use ASNavigationController with regular UIViewControllers, as well as ASViewControllers. - * It is safe to subclass or use even where AsyncDisplayKit is not adopted. - * - * @see ASManagesChildVisibilityDepth - */ -@interface ASNavigationController : UINavigationController - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASNavigationController.mm b/submodules/AsyncDisplayKit/Source/ASNavigationController.mm deleted file mode 100644 index c92ddd7d27..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASNavigationController.mm +++ /dev/null @@ -1,115 +0,0 @@ -// -// ASNavigationController.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// -#ifndef MINIMAL_ASDK -#import -#import -#import - -@implementation ASNavigationController -{ - BOOL _parentManagesVisibilityDepth; - NSInteger _visibilityDepth; -} - -ASVisibilityDidMoveToParentViewController; - -ASVisibilityViewWillAppear; - -ASVisibilityViewDidDisappearImplementation; - -ASVisibilitySetVisibilityDepth; - -ASVisibilityDepthImplementation; - -- (void)visibilityDepthDidChange -{ - for (UIViewController *viewController in self.viewControllers) { - if ([viewController conformsToProtocol:@protocol(ASVisibilityDepth)]) { - [(id )viewController visibilityDepthDidChange]; - } - } -} - -- (NSInteger)visibilityDepthOfChildViewController:(UIViewController *)childViewController -{ - NSUInteger viewControllerIndex = [self.viewControllers indexOfObjectIdenticalTo:childViewController]; - if (viewControllerIndex == NSNotFound) { - //If childViewController is not actually a child, return NSNotFound which is also a really large number. - return NSNotFound; - } - - if (viewControllerIndex == self.viewControllers.count - 1) { - //view controller is at the top, just return our own visibility depth. - return [self visibilityDepth]; - } else if (viewControllerIndex == 0) { - //view controller is the root view controller. Can be accessed by holding the back button. - return [self visibilityDepth] + 1; - } - - return [self visibilityDepth] + self.viewControllers.count - 1 - viewControllerIndex; -} - -#pragma mark - UIKit overrides - -- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated -{ - as_activity_create_for_scope("Pop multiple from ASNavigationController"); - NSArray *viewControllers = [super popToViewController:viewController animated:animated]; - as_log_info(ASNodeLog(), "Popped %@ to %@, removing %@", self, viewController, ASGetDescriptionValueString(viewControllers)); - - [self visibilityDepthDidChange]; - return viewControllers; -} - -- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated -{ - as_activity_create_for_scope("Pop to root of ASNavigationController"); - NSArray *viewControllers = [super popToRootViewControllerAnimated:animated]; - as_log_info(ASNodeLog(), "Popped view controllers %@ from %@", ASGetDescriptionValueString(viewControllers), self); - - [self visibilityDepthDidChange]; - return viewControllers; -} - -- (void)setViewControllers:(NSArray *)viewControllers -{ - // NOTE: As of now this method calls through to setViewControllers:animated: so no need to log/activity here. - - [super setViewControllers:viewControllers]; - [self visibilityDepthDidChange]; -} - -- (void)setViewControllers:(NSArray *)viewControllers animated:(BOOL)animated -{ - as_activity_create_for_scope("Set view controllers of ASNavigationController"); - as_log_info(ASNodeLog(), "Set view controllers of %@ to %@ animated: %d", self, ASGetDescriptionValueString(viewControllers), animated); - [super setViewControllers:viewControllers animated:animated]; - [self visibilityDepthDidChange]; -} - -- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated -{ - as_activity_create_for_scope("Push view controller on ASNavigationController"); - as_log_info(ASNodeLog(), "Pushing %@ onto %@", viewController, self); - [super pushViewController:viewController animated:animated]; - [self visibilityDepthDidChange]; -} - -- (UIViewController *)popViewControllerAnimated:(BOOL)animated -{ - as_activity_create_for_scope("Pop view controller from ASNavigationController"); - UIViewController *viewController = [super popViewControllerAnimated:animated]; - as_log_info(ASNodeLog(), "Popped %@ from %@", viewController, self); - [self visibilityDepthDidChange]; - return viewController; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASNetworkImageLoadInfo.h b/submodules/AsyncDisplayKit/Source/ASNetworkImageLoadInfo.h deleted file mode 100644 index 55c4b49a7a..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASNetworkImageLoadInfo.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// ASNetworkImageLoadInfo.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -typedef NS_ENUM(NSInteger, ASNetworkImageSourceType) { - ASNetworkImageSourceUnspecified = 0, - ASNetworkImageSourceSynchronousCache, - ASNetworkImageSourceAsynchronousCache, - ASNetworkImageSourceFileURL, - ASNetworkImageSourceDownload, -}; - -AS_SUBCLASSING_RESTRICTED -@interface ASNetworkImageLoadInfo : NSObject - -/// The type of source from which the image was loaded. -@property (readonly) ASNetworkImageSourceType sourceType; - -/// The image URL that was downloaded. -@property (readonly) NSURL *url; - -/// The download identifier, if one was provided. -@property (nullable, readonly) id downloadIdentifier; - -/// The userInfo object provided by the downloader, if one was provided. -@property (nullable, readonly) id userInfo; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASNetworkImageLoadInfo.mm b/submodules/AsyncDisplayKit/Source/ASNetworkImageLoadInfo.mm deleted file mode 100644 index 4c3d553e74..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASNetworkImageLoadInfo.mm +++ /dev/null @@ -1,32 +0,0 @@ -// -// ASNetworkImageLoadInfo.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -@implementation ASNetworkImageLoadInfo - -- (instancetype)initWithURL:(NSURL *)url sourceType:(ASNetworkImageSourceType)sourceType downloadIdentifier:(id)downloadIdentifier userInfo:(id)userInfo -{ - if (self = [super init]) { - _url = [url copy]; - _sourceType = sourceType; - _downloadIdentifier = downloadIdentifier; - _userInfo = userInfo; - } - return self; -} - -#pragma mark - NSCopying - -- (id)copyWithZone:(NSZone *)zone -{ - return self; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASNetworkImageNode.h b/submodules/AsyncDisplayKit/Source/ASNetworkImageNode.h deleted file mode 100644 index f67826e86c..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASNetworkImageNode.h +++ /dev/null @@ -1,241 +0,0 @@ -// -// ASNetworkImageNode.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -NS_ASSUME_NONNULL_BEGIN - -@protocol ASNetworkImageNodeDelegate, ASImageCacheProtocol, ASImageDownloaderProtocol; -@class ASNetworkImageLoadInfo; - - -/** - * ASNetworkImageNode is a simple image node that can download and display an image from the network, with support for a - * placeholder image (). The currently-displayed image is always available in the inherited ASImageNode - * property. - * - * @see ASMultiplexImageNode for a more powerful counterpart to this class. - */ -@interface ASNetworkImageNode : ASImageNode - -/** - * The designated initializer. Cache and Downloader are WEAK references. - * - * @param cache The object that implements a cache of images for the image node. Weak reference. - * @param downloader The object that implements image downloading for the image node. Must not be nil. Weak reference. - * - * @discussion If `cache` is nil, the receiver will not attempt to retrieve images from a cache before downloading them. - * - * @return An initialized ASNetworkImageNode. - */ -- (instancetype)initWithCache:(nullable id)cache downloader:(id)downloader NS_DESIGNATED_INITIALIZER; - -/** - * Convenience initializer. - * - * @return An ASNetworkImageNode configured to use the NSURLSession-powered ASBasicImageDownloader, and no extra cache. - */ -- (instancetype)init; - -/** - * The delegate, which must conform to the protocol. - */ -@property (nullable, weak) id delegate; - -/** - * The delegate will receive callbacks on main thread. Default to YES. - */ -@property (class) BOOL useMainThreadDelegateCallbacks; - -/** - * The image to display. - * - * @discussion By setting an image to the image property the ASNetworkImageNode will act like a plain ASImageNode. - * As soon as the URL is set the ASNetworkImageNode will act like an ASNetworkImageNode and the image property - * will be managed internally. This means the image property will be cleared out and replaced by the placeholder - * () image while loading and the final image after the new image data was downloaded and processed. - * If you want to use a placholder image functionality use the defaultImage property instead. - */ -@property (nullable) UIImage *image; - -/** - * A placeholder image to display while the URL is loading. This is slightly different than placeholderImage in the - * ASDisplayNode superclass as defaultImage will *not* be displayed synchronously. If you wish to have the image - * displayed synchronously, use @c placeholderImage. - */ -@property (nullable) UIImage *defaultImage; - -/** - * The URL of a new image to download and display. - * - * @discussion By setting an URL, the image property of this node will be managed internally. This means previously - * directly set images to the image property will be cleared out and replaced by the placeholder () image - * while loading and the final image after the new image data was downloaded and processed. - */ -@property (nullable, copy) NSURL *URL; - -/** - * An array of URLs of increasing cost to download. - * - * @discussion By setting an array of URLs, the image property of this node will be managed internally. This means previously - * directly set images to the image property will be cleared out and replaced by the placeholder () image - * while loading and the final image after the new image data was downloaded and processed. - * - * @deprecated This API has been removed for now due to the increased complexity to the class that it brought. - * Please use .URL instead. - */ -@property (nullable, copy) NSArray *URLs ASDISPLAYNODE_DEPRECATED_MSG("Please use URL instead."); - -/** - * Download and display a new image. - * - * @param URL The URL of a new image to download and display. - * @param reset Whether to display a placeholder () while loading the new image. - * - * @discussion By setting an URL, the image property of this node will be managed internally. This means previously - * directly set images to the image property will be cleared out and replaced by the placeholder () image - * while loading and the final image after the new image data was downloaded and processed. - */ -- (void)setURL:(nullable NSURL *)URL resetToDefault:(BOOL)reset; - -/** - * If is a local file, set this property to YES to take advantage of UIKit's image caching. Defaults to YES. - */ -@property BOOL shouldCacheImage; - -/** - * If the downloader implements progressive image rendering and this value is YES progressive renders of the - * image will be displayed as the image downloads. Regardless of this properties value, progress renders will - * only occur when the node is visible. Defaults to YES. - */ -@property BOOL shouldRenderProgressImages; - -/** - * The image quality of the current image. - * - * If the URL is set, this is a number between 0 and 1 and can be used to track - * progressive progress. Calculated by dividing number of bytes / expected number of total bytes. - * This is zero until the first progressive render or the final display. - * - * If the URL is unset, this is 1 if defaultImage or image is set to non-nil. - * - */ -@property (readonly) CGFloat currentImageQuality; - -/** - * The currentImageQuality (value between 0 and 1) of the last image that completed displaying. - */ -@property (readonly) CGFloat renderedImageQuality; - -@end - - -#pragma mark - - -/** - * The methods declared by the ASNetworkImageNodeDelegate protocol allow the adopting delegate to respond to - * notifications such as finished decoding and downloading an image. - */ -@protocol ASNetworkImageNodeDelegate -@optional - -/** - * Notification that the image node started to load - * - * @param imageNode The sender. - * - * @discussion Called on the main thread. - */ -- (void)imageNodeDidStartFetchingData:(ASNetworkImageNode *)imageNode; - -/** - * Notification that the image node will load image from cache - * - * @param imageNode The sender. - * - * @discussion Called on the main thread. - */ -- (void)imageNodeWillLoadImageFromCache:(ASNetworkImageNode *)imageNode; - -/** - * Notification that the image node finished loading image from cache - * - * @param imageNode The sender. - * - * @discussion Called on the main thread. - */ -- (void)imageNodeDidLoadImageFromCache:(ASNetworkImageNode *)imageNode; - -/** - * Notification that the image node will load image from network - * - * @param imageNode The sender. - * - * @discussion Called on the main thread. - */ -- (void)imageNodeWillLoadImageFromNetwork:(ASNetworkImageNode *)imageNode; - -/** - * Notification that the image node will start display - * - * @param imageNode The sender. - * - * @discussion Called on the main thread. - */ -- (void)imageNodeWillStartDisplayAsynchronously:(ASNetworkImageNode *)imageNode; - -/** - * Notification that the image node finished downloading an image, with additional info. - * If implemented, this method will be called instead of `imageNode:didLoadImage:`. - * - * @param imageNode The sender. - * @param image The newly-loaded image. - * @param info Additional information about the image load. - * - * @discussion Called on the main thread if useMainThreadDelegateCallbacks=YES (the default), otherwise on a background thread. - */ -- (void)imageNode:(ASNetworkImageNode *)imageNode didLoadImage:(UIImage *)image info:(ASNetworkImageLoadInfo *)info; - -/** - * Notification that the image node finished downloading an image. - * - * @param imageNode The sender. - * @param image The newly-loaded image. - * - * @discussion Called on the main thread if useMainThreadDelegateCallbacks=YES (the default), otherwise on a background thread. - */ -- (void)imageNode:(ASNetworkImageNode *)imageNode didLoadImage:(UIImage *)image; - -/** - * Notification that the image node failed to download the image. - * - * @param imageNode The sender. - * @param error The error with details. - * - * @discussion Called on the main thread if useMainThreadDelegateCallbacks=YES (the default), otherwise on a background thread. - */ -- (void)imageNode:(ASNetworkImageNode *)imageNode didFailWithError:(NSError *)error; - -/** - * Notification that the image node finished decoding an image. - * - * @param imageNode The sender. - * - * @discussion Called on the main thread. - */ -- (void)imageNodeDidFinishDecoding:(ASNetworkImageNode *)imageNode; - - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASNetworkImageNode.mm b/submodules/AsyncDisplayKit/Source/ASNetworkImageNode.mm deleted file mode 100644 index 29a600d8e8..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASNetworkImageNode.mm +++ /dev/null @@ -1,892 +0,0 @@ -// -// ASNetworkImageNode.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#import - -#if AS_PIN_REMOTE_IMAGE -#import -#endif - -@interface ASNetworkImageNode () -{ - // Only access any of these while locked. - __weak id _delegate; - - NSURL *_URL; - UIImage *_defaultImage; - - NSInteger _cacheSentinel; - id _downloadIdentifier; - // The download identifier that we have set a progress block on, if any. - id _downloadIdentifierForProgressBlock; - - BOOL _imageLoaded; - BOOL _imageWasSetExternally; - CGFloat _currentImageQuality; - CGFloat _renderedImageQuality; - BOOL _shouldRenderProgressImages; - - struct { - unsigned int delegateWillStartDisplayAsynchronously:1; - unsigned int delegateWillLoadImageFromCache:1; - unsigned int delegateWillLoadImageFromNetwork:1; - unsigned int delegateDidStartFetchingData:1; - unsigned int delegateDidFailWithError:1; - unsigned int delegateDidFinishDecoding:1; - unsigned int delegateDidLoadImage:1; - unsigned int delegateDidLoadImageFromCache:1; - unsigned int delegateDidLoadImageWithInfo:1; - } _delegateFlags; - - - // Immutable and set on init only. We don't need to lock in this case. - __weak id _downloader; - struct { - unsigned int downloaderImplementsSetProgress:1; - unsigned int downloaderImplementsSetPriority:1; - unsigned int downloaderImplementsAnimatedImage:1; - unsigned int downloaderImplementsCancelWithResume:1; - unsigned int downloaderImplementsDownloadWithPriority:1; - } _downloaderFlags; - - // Immutable and set on init only. We don't need to lock in this case. - __weak id _cache; - struct { - unsigned int cacheSupportsClearing:1; - unsigned int cacheSupportsSynchronousFetch:1; - } _cacheFlags; -} - -@end - -@implementation ASNetworkImageNode - -static std::atomic_bool _useMainThreadDelegateCallbacks(true); - -@dynamic image; - -- (instancetype)initWithCache:(id)cache downloader:(id)downloader -{ - if (!(self = [super init])) - return nil; - - _cache = (id)cache; - _downloader = (id)downloader; - - _downloaderFlags.downloaderImplementsSetProgress = [downloader respondsToSelector:@selector(setProgressImageBlock:callbackQueue:withDownloadIdentifier:)]; - _downloaderFlags.downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)]; - _downloaderFlags.downloaderImplementsAnimatedImage = [downloader respondsToSelector:@selector(animatedImageWithData:)]; - _downloaderFlags.downloaderImplementsCancelWithResume = [downloader respondsToSelector:@selector(cancelImageDownloadWithResumePossibilityForIdentifier:)]; - _downloaderFlags.downloaderImplementsDownloadWithPriority = [downloader respondsToSelector:@selector(downloadImageWithURL:priority:callbackQueue:downloadProgress:completion:)]; - - _cacheFlags.cacheSupportsClearing = [cache respondsToSelector:@selector(clearFetchedImageFromCacheWithURL:)]; - _cacheFlags.cacheSupportsSynchronousFetch = [cache respondsToSelector:@selector(synchronouslyFetchedCachedImageWithURL:)]; - - _shouldCacheImage = YES; - _shouldRenderProgressImages = YES; - self.shouldBypassEnsureDisplay = YES; - - return self; -} - -- (instancetype)init -{ -#if AS_PIN_REMOTE_IMAGE - return [self initWithCache:[ASPINRemoteImageDownloader sharedDownloader] downloader:[ASPINRemoteImageDownloader sharedDownloader]]; -#else - return [self initWithCache:nil downloader:[ASBasicImageDownloader sharedImageDownloader]]; -#endif -} - -- (void)dealloc -{ - [self _cancelImageDownloadWithResumePossibility:NO]; -} - -- (dispatch_queue_t)callbackQueue -{ - return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); -} - -#pragma mark - Public methods -- must lock - -/// Setter for public image property. It has the side effect of setting an internal _imageWasSetExternally that prevents setting an image internally. Setting an image internally should happen with the _setImage: method -- (void)setImage:(UIImage *)image -{ - ASLockScopeSelf(); - [self _locked_setImage:image]; -} - -- (void)_locked_setImage:(UIImage *)image -{ - ASAssertLocked(__instanceLock__); - - BOOL imageWasSetExternally = (image != nil); - BOOL shouldCancelAndClear = imageWasSetExternally && (imageWasSetExternally != _imageWasSetExternally); - _imageWasSetExternally = imageWasSetExternally; - if (shouldCancelAndClear) { - ASDisplayNodeAssertNil(_URL, @"Directly setting an image on an ASNetworkImageNode causes it to behave like an ASImageNode instead of an ASNetworkImageNode. If this is what you want, set the URL to nil first."); - _URL = nil; - [self _locked_cancelDownloadAndClearImageWithResumePossibility:NO]; - } - - // If our image is being set externally, the image quality is 100% - if (imageWasSetExternally) { - [self _setCurrentImageQuality:1.0]; - } - - [self _locked__setImage:image]; -} - -/// Setter for private image property. See @c _locked_setImage why this is needed -- (void)_setImage:(UIImage *)image -{ - ASLockScopeSelf(); - [self _locked__setImage:image]; -} - -- (void)_locked__setImage:(UIImage *)image -{ - ASAssertLocked(__instanceLock__); - [super _locked_setImage:image]; -} - -// Deprecated -- (void)setURLs:(NSArray *)URLs -{ - [self setURL:[URLs firstObject]]; -} - -// Deprecated -- (NSArray *)URLs -{ - return @[self.URL]; -} - -- (void)setURL:(NSURL *)URL -{ - [self setURL:URL resetToDefault:YES]; -} - -- (void)setURL:(NSURL *)URL resetToDefault:(BOOL)reset -{ - { - ASLockScopeSelf(); - - if (ASObjectIsEqual(URL, _URL)) { - return; - } - - URL = [URL copy]; - - ASDisplayNodeAssert(_imageWasSetExternally == NO, @"Setting a URL to an ASNetworkImageNode after setting an image changes its behavior from an ASImageNode to an ASNetworkImageNode. If this is what you want, set the image to nil first."); - - _imageWasSetExternally = NO; - - [self _locked_cancelImageDownloadWithResumePossibility:NO]; - - _imageLoaded = NO; - - _URL = URL; - - // If URL is nil and URL was not equal to _URL (checked at the top), then we previously had a URL but it's been nil'd out. - BOOL hadURL = (URL == nil); - if (reset || hadURL) { - [self _setCurrentImageQuality:(hadURL ? 0.0 : 1.0)]; - [self _locked__setImage:_defaultImage]; - } - } - - [self setNeedsPreload]; -} - -- (NSURL *)URL -{ - return ASLockedSelf(_URL); -} - -- (void)setDefaultImage:(UIImage *)defaultImage -{ - ASLockScopeSelf(); - - [self _locked_setDefaultImage:defaultImage]; -} - -- (void)_locked_setDefaultImage:(UIImage *)defaultImage -{ - if (ASObjectIsEqual(defaultImage, _defaultImage)) { - return; - } - - _defaultImage = defaultImage; - - if (!_imageLoaded) { - [self _setCurrentImageQuality:((_URL == nil) ? 0.0 : 1.0)]; - [self _locked__setImage:defaultImage]; - } -} - -- (UIImage *)defaultImage -{ - return ASLockedSelf(_defaultImage); -} - -- (void)setCurrentImageQuality:(CGFloat)currentImageQuality -{ - ASLockScopeSelf(); - _currentImageQuality = currentImageQuality; -} - -- (CGFloat)currentImageQuality -{ - return ASLockedSelf(_currentImageQuality); -} - -/** - * Always use these methods internally to update the current image quality - * We want to maintain the order that currentImageQuality is set regardless of the calling thread, - * so we always have to dispatch to the main thread to ensure that we queue the operations in the correct order. - * (see comment in displayDidFinish) - */ -- (void)_setCurrentImageQuality:(CGFloat)imageQuality -{ - dispatch_async(dispatch_get_main_queue(), ^{ - self.currentImageQuality = imageQuality; - }); -} - -- (void)setRenderedImageQuality:(CGFloat)renderedImageQuality -{ - ASLockScopeSelf(); - _renderedImageQuality = renderedImageQuality; -} - -- (CGFloat)renderedImageQuality -{ - ASLockScopeSelf(); - return _renderedImageQuality; -} - -- (void)setDelegate:(id)delegate -{ - ASLockScopeSelf(); - _delegate = delegate; - - _delegateFlags.delegateWillStartDisplayAsynchronously = [delegate respondsToSelector:@selector(imageNodeWillStartDisplayAsynchronously:)]; - _delegateFlags.delegateWillLoadImageFromCache = [delegate respondsToSelector:@selector(imageNodeWillLoadImageFromCache:)]; - _delegateFlags.delegateWillLoadImageFromNetwork = [delegate respondsToSelector:@selector(imageNodeWillLoadImageFromNetwork:)]; - _delegateFlags.delegateDidStartFetchingData = [delegate respondsToSelector:@selector(imageNodeDidStartFetchingData:)]; - _delegateFlags.delegateDidFailWithError = [delegate respondsToSelector:@selector(imageNode:didFailWithError:)]; - _delegateFlags.delegateDidFinishDecoding = [delegate respondsToSelector:@selector(imageNodeDidFinishDecoding:)]; - _delegateFlags.delegateDidLoadImage = [delegate respondsToSelector:@selector(imageNode:didLoadImage:)]; - _delegateFlags.delegateDidLoadImageFromCache = [delegate respondsToSelector:@selector(imageNodeDidLoadImageFromCache:)]; - _delegateFlags.delegateDidLoadImageWithInfo = [delegate respondsToSelector:@selector(imageNode:didLoadImage:info:)]; -} - -- (id)delegate -{ - ASLockScopeSelf(); - return _delegate; -} - -- (void)setShouldRenderProgressImages:(BOOL)shouldRenderProgressImages -{ - if (ASLockedSelfCompareAssign(_shouldRenderProgressImages, shouldRenderProgressImages)) { - [self _updateProgressImageBlockOnDownloaderIfNeeded]; - } -} - -- (BOOL)shouldRenderProgressImages -{ - ASLockScopeSelf(); - return _shouldRenderProgressImages; -} - -- (BOOL)placeholderShouldPersist -{ - ASLockScopeSelf(); - return (self.image == nil && self.animatedImage == nil && _URL != nil); -} - -/* displayWillStartAsynchronously: in ASMultiplexImageNode has a very similar implementation. Changes here are likely necessary - in ASMultiplexImageNode as well. */ -- (void)displayWillStartAsynchronously:(BOOL)asynchronously -{ - [super displayWillStartAsynchronously:asynchronously]; - - id delegate; - BOOL notifyDelegate; - { - ASLockScopeSelf(); - notifyDelegate = _delegateFlags.delegateWillStartDisplayAsynchronously; - delegate = _delegate; - } - if (notifyDelegate) { - [delegate imageNodeWillStartDisplayAsynchronously:self]; - } - - if (asynchronously == NO && _cacheFlags.cacheSupportsSynchronousFetch) { - ASLockScopeSelf(); - - NSURL *url = _URL; - if (_imageLoaded == NO && url && _downloadIdentifier == nil) { - UIImage *result = [[_cache synchronouslyFetchedCachedImageWithURL:url] asdk_image]; - if (result) { - [self _setCurrentImageQuality:1.0]; - [self _locked__setImage:result]; - _imageLoaded = YES; - - // Call out to the delegate. - if (_delegateFlags.delegateDidLoadImageWithInfo) { - ASUnlockScope(self); - const auto info = [[ASNetworkImageLoadInfo alloc] initWithURL:url sourceType:ASNetworkImageSourceSynchronousCache downloadIdentifier:nil userInfo:nil]; - [delegate imageNode:self didLoadImage:result info:info]; - } else if (_delegateFlags.delegateDidLoadImage) { - ASUnlockScope(self); - [delegate imageNode:self didLoadImage:result]; - } - } - } - } - - if (self.image == nil) { - [self _updatePriorityOnDownloaderIfNeededWithDefaultPriority:ASImageDownloaderPriorityImminent]; - } -} - -/* visibileStateDidChange in ASMultiplexImageNode has a very similar implementation. Changes here are likely necessary - in ASMultiplexImageNode as well. */ -- (void)didEnterVisibleState -{ - [super didEnterVisibleState]; - [self _updatePriorityOnDownloaderIfNeededWithDefaultPriority:ASImageDownloaderPriorityVisible]; - [self _updateProgressImageBlockOnDownloaderIfNeeded]; -} - -- (void)didExitVisibleState -{ - [super didExitVisibleState]; - [self _updatePriorityOnDownloaderIfNeededWithDefaultPriority:ASImageDownloaderPriorityPreload]; - [self _updateProgressImageBlockOnDownloaderIfNeeded]; -} - -- (void)didExitDisplayState -{ - [super didExitDisplayState]; - if (ASActivateExperimentalFeature(ASExperimentalImageDownloaderPriority)) { - [self _updatePriorityOnDownloaderIfNeededWithDefaultPriority:ASImageDownloaderPriorityPreload]; - } -} - -- (void)didExitPreloadState -{ - [super didExitPreloadState]; - - // If the image was set explicitly we don't want to remove it while exiting the preload state - if (ASLockedSelf(_imageWasSetExternally)) { - return; - } - - [self _cancelDownloadAndClearImageWithResumePossibility:YES]; -} - -- (void)didEnterPreloadState -{ - [super didEnterPreloadState]; - - // Image was set externally no need to load an image - [self _lazilyLoadImageIfNecessary]; -} - -+ (void)setUseMainThreadDelegateCallbacks:(BOOL)useMainThreadDelegateCallbacks -{ - _useMainThreadDelegateCallbacks = useMainThreadDelegateCallbacks; -} - -+ (BOOL)useMainThreadDelegateCallbacks -{ - return _useMainThreadDelegateCallbacks; -} - -#pragma mark - Progress - -- (void)handleProgressImage:(UIImage *)progressImage progress:(CGFloat)progress downloadIdentifier:(nullable id)downloadIdentifier -{ - ASLockScopeSelf(); - - // Getting a result back for a different download identifier, download must not have been successfully canceled - if (ASObjectIsEqual(_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { - return; - } - - as_log_verbose(ASImageLoadingLog(), "Received progress image for %@ q: %.2g id: %@", self, progress, progressImage); - [self _setCurrentImageQuality:progress]; - [self _locked__setImage:progressImage]; -} - -- (void)_updatePriorityOnDownloaderIfNeededWithDefaultPriority:(ASImageDownloaderPriority)defaultPriority -{ - if (_downloaderFlags.downloaderImplementsSetPriority) { - ASLockScopeSelf(); - - if (_downloadIdentifier != nil) { - ASImageDownloaderPriority priority = defaultPriority; - if (ASActivateExperimentalFeature(ASExperimentalImageDownloaderPriority)) { - priority = ASImageDownloaderPriorityWithInterfaceState(_interfaceState); - } - - [_downloader setPriority:priority withDownloadIdentifier:_downloadIdentifier]; - } - } -} - -- (void)_updateProgressImageBlockOnDownloaderIfNeeded -{ - // If the downloader doesn't do progress, we are done. - if (_downloaderFlags.downloaderImplementsSetProgress == NO) { - return; - } - - // Read state. - [self lock]; - BOOL shouldRender = _shouldRenderProgressImages && ASInterfaceStateIncludesVisible(_interfaceState); - id oldDownloadIDForProgressBlock = _downloadIdentifierForProgressBlock; - id newDownloadIDForProgressBlock = shouldRender ? _downloadIdentifier : nil; - BOOL clearAndReattempt = NO; - [self unlock]; - - // If we're already bound to the correct download, we're done. - if (ASObjectIsEqual(oldDownloadIDForProgressBlock, newDownloadIDForProgressBlock)) { - return; - } - - // Unbind from the previous download. - if (oldDownloadIDForProgressBlock != nil) { - as_log_verbose(ASImageLoadingLog(), "Disabled progress images for %@ id: %@", self, oldDownloadIDForProgressBlock); - [_downloader setProgressImageBlock:nil callbackQueue:[self callbackQueue] withDownloadIdentifier:oldDownloadIDForProgressBlock]; - } - - // Bind to the current download. - if (newDownloadIDForProgressBlock != nil) { - __weak __typeof(self) weakSelf = self; - as_log_verbose(ASImageLoadingLog(), "Enabled progress images for %@ id: %@", self, newDownloadIDForProgressBlock); - [_downloader setProgressImageBlock:^(UIImage * _Nonnull progressImage, CGFloat progress, id _Nullable downloadIdentifier) { - [weakSelf handleProgressImage:progressImage progress:progress downloadIdentifier:downloadIdentifier]; - } callbackQueue:[self callbackQueue] withDownloadIdentifier:newDownloadIDForProgressBlock]; - } - - // Update state local state with lock held. - { - ASLockScopeSelf(); - // Check if the oldDownloadIDForProgressBlock still is the same as the _downloadIdentifierForProgressBlock - if (_downloadIdentifierForProgressBlock == oldDownloadIDForProgressBlock) { - _downloadIdentifierForProgressBlock = newDownloadIDForProgressBlock; - } else if (newDownloadIDForProgressBlock != nil) { - // If this is not the case another thread did change the _downloadIdentifierForProgressBlock already so - // we have to deregister the newDownloadIDForProgressBlock that we registered above - clearAndReattempt = YES; - } - } - - if (clearAndReattempt) { - // In this case another thread changed the _downloadIdentifierForProgressBlock before we finished registering - // the new progress block for newDownloadIDForProgressBlock ID. Let's clear it now and reattempt to register - if (newDownloadIDForProgressBlock) { - [_downloader setProgressImageBlock:nil callbackQueue:[self callbackQueue] withDownloadIdentifier:newDownloadIDForProgressBlock]; - } - [self _updateProgressImageBlockOnDownloaderIfNeeded]; - } -} - -- (void)_cancelDownloadAndClearImageWithResumePossibility:(BOOL)storeResume -{ - ASLockScopeSelf(); - [self _locked_cancelDownloadAndClearImageWithResumePossibility:storeResume]; -} - -- (void)_locked_cancelDownloadAndClearImageWithResumePossibility:(BOOL)storeResume -{ - ASAssertLocked(__instanceLock__); - - [self _locked_cancelImageDownloadWithResumePossibility:storeResume]; - - [self _locked_setAnimatedImage:nil]; - [self _setCurrentImageQuality:0.0]; - [self _locked__setImage:_defaultImage]; - - _imageLoaded = NO; - - if (_cacheFlags.cacheSupportsClearing) { - if (_URL != nil) { - as_log_verbose(ASImageLoadingLog(), "Clearing cached image for %@ url: %@", self, _URL); - [_cache clearFetchedImageFromCacheWithURL:_URL]; - } - } -} - -- (void)_cancelImageDownloadWithResumePossibility:(BOOL)storeResume -{ - ASLockScopeSelf(); - [self _locked_cancelImageDownloadWithResumePossibility:storeResume]; -} - -- (void)_locked_cancelImageDownloadWithResumePossibility:(BOOL)storeResume -{ - ASAssertLocked(__instanceLock__); - - if (!_downloadIdentifier) { - return; - } - - if (_downloadIdentifier) { - if (storeResume && _downloaderFlags.downloaderImplementsCancelWithResume) { - as_log_verbose(ASImageLoadingLog(), "Canceling image download w resume for %@ id: %@", self, _downloadIdentifier); - [_downloader cancelImageDownloadWithResumePossibilityForIdentifier:_downloadIdentifier]; - } else { - as_log_verbose(ASImageLoadingLog(), "Canceling image download no resume for %@ id: %@", self, _downloadIdentifier); - [_downloader cancelImageDownloadForIdentifier:_downloadIdentifier]; - } - } - _downloadIdentifier = nil; - _cacheSentinel++; -} - -- (void)_downloadImageWithCompletion:(void (^)(id imageContainer, NSError*, id downloadIdentifier, id userInfo))finished -{ - ASPerformBlockOnBackgroundThread(^{ - NSURL *url; - id downloadIdentifier; - BOOL cancelAndReattempt = NO; - ASInterfaceState interfaceState; - - // Below, to avoid performance issues, we're calling downloadImageWithURL without holding the lock. This is a bit ugly because - // We need to reobtain the lock after and ensure that the task we've kicked off still matches our URL. If not, we need to cancel - // it and try again. - { - ASLockScopeSelf(); - url = self->_URL; - interfaceState = self->_interfaceState; - } - - dispatch_queue_t callbackQueue = [self callbackQueue]; - ASImageDownloaderProgress downloadProgress = NULL; - ASImageDownloaderCompletion completion = ^(id _Nullable imageContainer, NSError * _Nullable error, id _Nullable downloadIdentifier, id _Nullable userInfo) { - if (finished != NULL) { - finished(imageContainer, error, downloadIdentifier, userInfo); - } - }; - - if (self->_downloaderFlags.downloaderImplementsDownloadWithPriority - && ASActivateExperimentalFeature(ASExperimentalImageDownloaderPriority)) { - /* - Decide a priority based on the current interface state of this node. - It can happen that this method was called when the node entered preload state - but the interface state, at this point, tells us that the node is (going to be) visible. - If that's the case, we jump to a higher priority directly. - */ - ASImageDownloaderPriority priority = ASImageDownloaderPriorityWithInterfaceState(interfaceState); - - downloadIdentifier = [self->_downloader downloadImageWithURL:url - priority:priority - callbackQueue:callbackQueue - downloadProgress:downloadProgress - completion:completion]; - } else { - /* - Kick off a download with default priority. - The actual "default" value is decided by the downloader. - ASBasicImageDownloader and ASPINRemoteImageDownloader both use ASImageDownloaderPriorityImminent - which is mapped to NSURLSessionTaskPriorityDefault. - - This means that preload and display nodes use the same priority - and their requests are put into the same pool. - */ - downloadIdentifier = [self->_downloader downloadImageWithURL:url - callbackQueue:callbackQueue - downloadProgress:downloadProgress - completion:completion]; - } - as_log_verbose(ASImageLoadingLog(), "Downloading image for %@ url: %@", self, url); - - { - ASLockScopeSelf(); - if (ASObjectIsEqual(self->_URL, url)) { - // The download we kicked off is correct, no need to do any more work. - self->_downloadIdentifier = downloadIdentifier; - } else { - // The URL changed since we kicked off our download task. This shouldn't happen often so we'll pay the cost and - // cancel that request and kick off a new one. - cancelAndReattempt = YES; - } - } - - if (cancelAndReattempt) { - if (downloadIdentifier != nil) { - as_log_verbose(ASImageLoadingLog(), "Canceling image download no resume for %@ id: %@", self, downloadIdentifier); - [self->_downloader cancelImageDownloadForIdentifier:downloadIdentifier]; - } - [self _downloadImageWithCompletion:finished]; - return; - } - - [self _updateProgressImageBlockOnDownloaderIfNeeded]; - }); -} - -- (void)_lazilyLoadImageIfNecessary -{ - ASDisplayNodeAssertMainThread(); - - [self lock]; - __weak id delegate = _delegate; - BOOL delegateDidStartFetchingData = _delegateFlags.delegateDidStartFetchingData; - BOOL delegateWillLoadImageFromCache = _delegateFlags.delegateWillLoadImageFromCache; - BOOL delegateWillLoadImageFromNetwork = _delegateFlags.delegateWillLoadImageFromNetwork; - BOOL delegateDidLoadImageFromCache = _delegateFlags.delegateDidLoadImageFromCache; - BOOL isImageLoaded = _imageLoaded; - NSURL *URL = _URL; - id currentDownloadIdentifier = _downloadIdentifier; - [self unlock]; - - if (!isImageLoaded && URL != nil && currentDownloadIdentifier == nil) { - if (delegateDidStartFetchingData) { - [delegate imageNodeDidStartFetchingData:self]; - } - - if (URL.isFileURL) { - dispatch_async(dispatch_get_main_queue(), ^{ - ASLockScopeSelf(); - - // Bail out if not the same URL anymore - if (!ASObjectIsEqual(URL, self->_URL)) { - return; - } - - if (self->_shouldCacheImage) { - [self _locked__setImage:[UIImage imageNamed:URL.path.lastPathComponent]]; - } else { - // First try to load the path directly, for efficiency assuming a developer who - // doesn't want caching is trying to be as minimal as possible. - auto nonAnimatedImage = [[UIImage alloc] initWithContentsOfFile:URL.path]; - if (nonAnimatedImage == nil) { - // If we couldn't find it, execute an -imageNamed:-like search so we can find resources even if the - // extension is not provided in the path. This allows the same path to work regardless of shouldCacheImage. - NSString *filename = [[NSBundle mainBundle] pathForResource:URL.path.lastPathComponent ofType:nil]; - if (filename != nil) { - nonAnimatedImage = [[UIImage alloc] initWithContentsOfFile:filename]; - } - } - - // If the file may be an animated gif and then created an animated image. - id animatedImage = nil; - if (self->_downloaderFlags.downloaderImplementsAnimatedImage) { - const auto data = [[NSData alloc] initWithContentsOfURL:URL]; - if (data != nil) { - animatedImage = [self->_downloader animatedImageWithData:data]; - - if ([animatedImage respondsToSelector:@selector(isDataSupported:)] && [animatedImage isDataSupported:data] == NO) { - animatedImage = nil; - } - } - } - - if (animatedImage != nil) { - [self _locked_setAnimatedImage:animatedImage]; - } else { - [self _locked__setImage:nonAnimatedImage]; - } - } - - self->_imageLoaded = YES; - - [self _setCurrentImageQuality:1.0]; - - if (self->_delegateFlags.delegateDidLoadImageWithInfo) { - ASUnlockScope(self); - const auto info = [[ASNetworkImageLoadInfo alloc] initWithURL:URL sourceType:ASNetworkImageSourceFileURL downloadIdentifier:nil userInfo:nil]; - [delegate imageNode:self didLoadImage:self.image info:info]; - } else if (self->_delegateFlags.delegateDidLoadImage) { - ASUnlockScope(self); - [delegate imageNode:self didLoadImage:self.image]; - } - }); - } else { - __weak __typeof__(self) weakSelf = self; - const auto finished = ^(id imageContainer, NSError *error, id downloadIdentifier, ASNetworkImageSourceType imageSource, id userInfo) { - ASPerformBlockOnBackgroundThread(^{ - __typeof__(self) strongSelf = weakSelf; - if (strongSelf == nil) { - return; - } - - as_log_verbose(ASImageLoadingLog(), "Downloaded image for %@ img: %@ url: %@", self, [imageContainer asdk_image], URL); - - // Grab the lock for the rest of the block - ASLockScope(strongSelf); - - //Getting a result back for a different download identifier, download must not have been successfully canceled - if (ASObjectIsEqual(strongSelf->_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { - return; - } - - //No longer in preload range, no point in setting the results (they won't be cleared in exit preload range) - if (ASInterfaceStateIncludesPreload(strongSelf->_interfaceState) == NO) { - strongSelf->_downloadIdentifier = nil; - strongSelf->_cacheSentinel++; - return; - } - - UIImage *newImage; - if (imageContainer != nil) { - [strongSelf _setCurrentImageQuality:1.0]; - NSData *animatedImageData = [imageContainer asdk_animatedImageData]; - if (animatedImageData && strongSelf->_downloaderFlags.downloaderImplementsAnimatedImage) { - id animatedImage = [strongSelf->_downloader animatedImageWithData:animatedImageData]; - [strongSelf _locked_setAnimatedImage:animatedImage]; - } else { - newImage = [imageContainer asdk_image]; - [strongSelf _locked__setImage:newImage]; - } - strongSelf->_imageLoaded = YES; - } - - strongSelf->_downloadIdentifier = nil; - strongSelf->_cacheSentinel++; - - void (^calloutBlock)(ASNetworkImageNode *inst); - - if (newImage) { - if (strongSelf->_delegateFlags.delegateDidLoadImageWithInfo) { - calloutBlock = ^(ASNetworkImageNode *strongSelf) { - const auto info = [[ASNetworkImageLoadInfo alloc] initWithURL:URL sourceType:imageSource downloadIdentifier:downloadIdentifier userInfo:userInfo]; - [delegate imageNode:strongSelf didLoadImage:newImage info:info]; - }; - } else if (strongSelf->_delegateFlags.delegateDidLoadImage) { - calloutBlock = ^(ASNetworkImageNode *strongSelf) { - [delegate imageNode:strongSelf didLoadImage:newImage]; - }; - } - } else if (error && strongSelf->_delegateFlags.delegateDidFailWithError) { - calloutBlock = ^(ASNetworkImageNode *strongSelf) { - [delegate imageNode:strongSelf didFailWithError:error]; - }; - } - - if (calloutBlock) { - if (ASNetworkImageNode.useMainThreadDelegateCallbacks) { - ASPerformBlockOnMainThread(^{ - if (auto strongSelf = weakSelf) { - calloutBlock(strongSelf); - } - }); - } else { - calloutBlock(strongSelf); - } - } - }); - }; - - // As the _cache and _downloader is only set once in the intializer we don't have to use a - // lock in here - if (_cache != nil) { - NSInteger cacheSentinel = ASLockedSelf(++_cacheSentinel); - - as_log_verbose(ASImageLoadingLog(), "Decaching image for %@ url: %@", self, URL); - - ASImageCacherCompletion completion = ^(id imageContainer) { - // If the cache sentinel changed, that means this request was cancelled. - if (ASLockedSelf(self->_cacheSentinel != cacheSentinel)) { - return; - } - - if ([imageContainer asdk_image] == nil && self->_downloader != nil) { - if (delegateWillLoadImageFromNetwork) { - [delegate imageNodeWillLoadImageFromNetwork:self]; - } - [self _downloadImageWithCompletion:^(id imageContainer, NSError *error, id downloadIdentifier, id userInfo) { - finished(imageContainer, error, downloadIdentifier, ASNetworkImageSourceDownload, userInfo); - }]; - } else { - if (delegateDidLoadImageFromCache) { - [delegate imageNodeDidLoadImageFromCache:self]; - } - as_log_verbose(ASImageLoadingLog(), "Decached image for %@ img: %@ url: %@", self, [imageContainer asdk_image], URL); - finished(imageContainer, nil, nil, ASNetworkImageSourceAsynchronousCache, nil); - } - }; - - if (delegateWillLoadImageFromCache) { - [delegate imageNodeWillLoadImageFromCache:self]; - } - [_cache cachedImageWithURL:URL - callbackQueue:[self callbackQueue] - completion:completion]; - } else { - if (delegateWillLoadImageFromNetwork) { - [delegate imageNodeWillLoadImageFromNetwork:self]; - } - [self _downloadImageWithCompletion:^(id imageContainer, NSError *error, id downloadIdentifier, id userInfo) { - finished(imageContainer, error, downloadIdentifier, ASNetworkImageSourceDownload, userInfo); - }]; - } - } - } -} - -#pragma mark - ASDisplayNode+Subclasses - -- (void)displayDidFinish -{ - [super displayDidFinish]; - - id delegate = nil; - - { - ASLockScopeSelf(); - if (_delegateFlags.delegateDidFinishDecoding && self.layer.contents != nil) { - /* We store the image quality in _currentImageQuality whenever _image is set. On the following displayDidFinish, we'll know that - _currentImageQuality is the quality of the image that has just finished rendering. In order for this to be accurate, we - need to be sure we are on main thread when we set _currentImageQuality. Otherwise, it is possible for _currentImageQuality - to be modified at a point where it is too late to cancel the main thread's previous display (the final sentinel check has passed), - but before the displayDidFinish of the previous display pass is called. In this situation, displayDidFinish would be called and we - would set _renderedImageQuality to the new _currentImageQuality, but the actual quality of the rendered image should be the previous - value stored in _currentImageQuality. */ - - _renderedImageQuality = _currentImageQuality; - - // Assign the delegate to be used - delegate = _delegate; - } - } - - if (delegate != nil) { - [delegate imageNodeDidFinishDecoding:self]; - } -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASNodeController+Beta.h b/submodules/AsyncDisplayKit/Source/ASNodeController+Beta.h deleted file mode 100644 index e9028f1986..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASNodeController+Beta.h +++ /dev/null @@ -1,59 +0,0 @@ -// -// ASNodeController+Beta.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import // for ASInterfaceState protocol - -/* ASNodeController is currently beta and open to change in the future */ -@interface ASNodeController<__covariant DisplayNodeType : ASDisplayNode *> - : NSObject - -@property (nonatomic, strong /* may be weak! */) DisplayNodeType node; - -// Until an ASNodeController can be provided in place of an ASCellNode, some apps may prefer to have -// nodes keep their controllers alive (and a weak reference from controller to node) - -@property (nonatomic) BOOL shouldInvertStrongReference; - -- (void)loadNode; - -// for descriptions see definition -- (void)nodeDidLoad ASDISPLAYNODE_REQUIRES_SUPER; -- (void)nodeDidLayout ASDISPLAYNODE_REQUIRES_SUPER; - -// This is only called during Yoga-driven layouts. -- (void)nodeWillCalculateLayout:(ASSizeRange)constrainedSize ASDISPLAYNODE_REQUIRES_SUPER; - -- (void)didEnterVisibleState ASDISPLAYNODE_REQUIRES_SUPER; -- (void)didExitVisibleState ASDISPLAYNODE_REQUIRES_SUPER; - -- (void)didEnterDisplayState ASDISPLAYNODE_REQUIRES_SUPER; -- (void)didExitDisplayState ASDISPLAYNODE_REQUIRES_SUPER; - -- (void)didEnterPreloadState ASDISPLAYNODE_REQUIRES_SUPER; -- (void)didExitPreloadState ASDISPLAYNODE_REQUIRES_SUPER; - -- (void)interfaceStateDidChange:(ASInterfaceState)newState - fromState:(ASInterfaceState)oldState ASDISPLAYNODE_REQUIRES_SUPER; - -- (void)hierarchyDisplayDidFinish ASDISPLAYNODE_REQUIRES_SUPER; - -/** - * @discussion Attempts (via ASLockSequence, a backing-off spinlock similar to - * std::lock()) to lock both the node and its ASNodeController, if one exists. - */ -- (ASLockSet)lockPair; - -@end - -@interface ASDisplayNode (ASNodeController) - -@property(nonatomic, readonly) ASNodeController *nodeController; - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASNodeController+Beta.mm b/submodules/AsyncDisplayKit/Source/ASNodeController+Beta.mm deleted file mode 100644 index 14aae33633..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASNodeController+Beta.mm +++ /dev/null @@ -1,135 +0,0 @@ -// -// ASNodeController+Beta.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import -#import - -#define _node (_shouldInvertStrongReference ? _weakNode : _strongNode) - -@implementation ASNodeController -{ - ASDisplayNode *_strongNode; - __weak ASDisplayNode *_weakNode; - AS::RecursiveMutex __instanceLock__; -} - -- (void)loadNode -{ - ASLockScopeSelf(); - self.node = [[ASDisplayNode alloc] init]; -} - -- (ASDisplayNode *)node -{ - ASLockScopeSelf(); - if (_node == nil) { - [self loadNode]; - } - return _node; -} - -- (void)setupReferencesWithNode:(ASDisplayNode *)node -{ - ASLockScopeSelf(); - if (_shouldInvertStrongReference) { - // The node should own the controller; weak reference from controller to node. - _weakNode = node; - _strongNode = nil; - } else { - // The controller should own the node; weak reference from node to controller. - _strongNode = node; - _weakNode = nil; - } - - [node __setNodeController:self]; -} - -- (void)setNode:(ASDisplayNode *)node -{ - ASLockScopeSelf(); - if (node == _node) { - return; - } - [self setupReferencesWithNode:node]; - [node addInterfaceStateDelegate:self]; -} - -- (void)setShouldInvertStrongReference:(BOOL)shouldInvertStrongReference -{ - ASLockScopeSelf(); - if (_shouldInvertStrongReference != shouldInvertStrongReference) { - // Because the BOOL controls which ivar we access, get the node before toggling. - ASDisplayNode *node = _node; - _shouldInvertStrongReference = shouldInvertStrongReference; - [self setupReferencesWithNode:node]; - } -} - -// subclass overrides -- (void)nodeDidLoad {} -- (void)nodeDidLayout {} -- (void)nodeWillCalculateLayout:(ASSizeRange)constrainedSize {} - -- (void)didEnterVisibleState {} -- (void)didExitVisibleState {} - -- (void)didEnterDisplayState {} -- (void)didExitDisplayState {} - -- (void)didEnterPreloadState {} -- (void)didExitPreloadState {} - -- (void)interfaceStateDidChange:(ASInterfaceState)newState - fromState:(ASInterfaceState)oldState {} - -- (void)hierarchyDisplayDidFinish {} - -- (ASLockSet)lockPair { - ASLockSet lockSet = ASLockSequence(^BOOL(ASAddLockBlock addLock) { - if (!addLock(_node)) { - return NO; - } - if (!addLock(self)) { - return NO; - } - return YES; - }); - - return lockSet; -} - -#pragma mark NSLocking - -- (void)lock -{ - __instanceLock__.lock(); -} - -- (void)unlock -{ - __instanceLock__.unlock(); -} - -- (BOOL)tryLock -{ - return __instanceLock__.try_lock(); -} - -@end - -@implementation ASDisplayNode (ASNodeController) - -- (ASNodeController *)nodeController -{ - return _weakNodeController ?: _strongNodeController; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/Details/ASObjectDescriptionHelpers.mm b/submodules/AsyncDisplayKit/Source/ASObjectDescriptionHelpers.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/Details/ASObjectDescriptionHelpers.mm rename to submodules/AsyncDisplayKit/Source/ASObjectDescriptionHelpers.mm index cbd6be0963..2b5d94492c 100644 --- a/submodules/AsyncDisplayKit/Source/Details/ASObjectDescriptionHelpers.mm +++ b/submodules/AsyncDisplayKit/Source/ASObjectDescriptionHelpers.mm @@ -11,7 +11,7 @@ #import -#import +#import "NSIndexSet+ASHelpers.h" NSString *ASGetDescriptionValueString(id object) { diff --git a/submodules/AsyncDisplayKit/Source/ASPagerFlowLayout.h b/submodules/AsyncDisplayKit/Source/ASPagerFlowLayout.h deleted file mode 100644 index 8b2f7e2684..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASPagerFlowLayout.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// ASPagerFlowLayout.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface ASPagerFlowLayout : UICollectionViewFlowLayout - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASPagerFlowLayout.mm b/submodules/AsyncDisplayKit/Source/ASPagerFlowLayout.mm deleted file mode 100644 index de20b08961..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASPagerFlowLayout.mm +++ /dev/null @@ -1,113 +0,0 @@ -// -// ASPagerFlowLayout.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// -#ifndef MINIMAL_ASDK - -#import -#import -#import - -@interface ASPagerFlowLayout () { - __weak ASCellNode *_currentCellNode; -} - -@end - -//TODO make this an ASCollectionViewLayout -@implementation ASPagerFlowLayout - -- (ASCollectionView *)asCollectionView -{ - // Dynamic cast is too slow and not worth it. - return (ASCollectionView *)self.collectionView; -} - -- (void)prepareLayout -{ - [super prepareLayout]; - if (_currentCellNode == nil) { - [self _updateCurrentNode]; - } -} - -- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset -{ - // Don't mess around if the user is interacting with the page node. Although if just a rotation happened we should - // try to use the current index path to not end up setting the target content offset to something in between pages - if (!self.collectionView.decelerating && !self.collectionView.tracking) { - NSIndexPath *indexPath = [self.asCollectionView indexPathForNode:_currentCellNode]; - if (indexPath) { - return [self _targetContentOffsetForItemAtIndexPath:indexPath proposedContentOffset:proposedContentOffset]; - } - } - - return [super targetContentOffsetForProposedContentOffset:proposedContentOffset]; -} - -- (CGPoint)_targetContentOffsetForItemAtIndexPath:(NSIndexPath *)indexPath proposedContentOffset:(CGPoint)proposedContentOffset -{ - if ([self _dataSourceIsEmpty]) { - return proposedContentOffset; - } - - UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath]; - if (attributes == nil) { - return proposedContentOffset; - } - - CGFloat xOffset = (CGRectGetWidth(self.collectionView.bounds) - CGRectGetWidth(attributes.frame)) / 2.0; - return CGPointMake(attributes.frame.origin.x - xOffset, proposedContentOffset.y); -} - -- (BOOL)_dataSourceIsEmpty -{ - return ([self.collectionView numberOfSections] == 0 || - [self.collectionView numberOfItemsInSection:0] == 0); -} - -- (void)_updateCurrentNode -{ - // Never change node during an animated bounds change (rotation) - // NOTE! Listening for -prepareForAnimatedBoundsChange and -finalizeAnimatedBoundsChange - // isn't sufficient here! It's broken! - NSArray *animKeys = self.collectionView.layer.animationKeys; - for (NSString *key in animKeys) { - if ([key hasPrefix:@"bounds"]) { - return; - } - } - - CGRect bounds = self.collectionView.bounds; - CGRect rect = CGRectMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds), 1, 1); - - NSIndexPath *indexPath = [self layoutAttributesForElementsInRect:rect].firstObject.indexPath; - if (indexPath) { - ASCellNode *node = [self.asCollectionView nodeForItemAtIndexPath:indexPath]; - if (node) { - _currentCellNode = node; - } - } -} - -- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds -{ - [self _updateCurrentNode]; - return [super shouldInvalidateLayoutForBoundsChange:newBounds]; -} - -- (UICollectionViewLayoutInvalidationContext *)invalidationContextForBoundsChange:(CGRect)newBounds -{ - UICollectionViewFlowLayoutInvalidationContext *ctx = (UICollectionViewFlowLayoutInvalidationContext *)[super invalidationContextForBoundsChange:newBounds]; - ctx.invalidateFlowLayoutDelegateMetrics = YES; - ctx.invalidateFlowLayoutAttributes = YES; - return ctx; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASPagerNode+Beta.h b/submodules/AsyncDisplayKit/Source/ASPagerNode+Beta.h deleted file mode 100644 index c4de4f45de..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASPagerNode+Beta.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// ASPagerNode+Beta.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK -#import - -@interface ASPagerNode (Beta) - -- (instancetype)initUsingAsyncCollectionLayout; - -@end -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASPagerNode.h b/submodules/AsyncDisplayKit/Source/ASPagerNode.h deleted file mode 100644 index b20f187f67..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASPagerNode.h +++ /dev/null @@ -1,137 +0,0 @@ -// -// ASPagerNode.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -@class ASPagerNode; -@class ASPagerFlowLayout; - -NS_ASSUME_NONNULL_BEGIN - -#define ASPagerNodeDataSource ASPagerDataSource -@protocol ASPagerDataSource - -/** - * This method replaces -collectionView:numberOfItemsInSection: - * - * @param pagerNode The sender. - * @return The total number of pages that can display in the pagerNode. - */ -- (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode; - -@optional - -/** - * This method replaces -collectionView:nodeForItemAtIndexPath: - * - * @param pagerNode The sender. - * @param index The index of the requested node. - * @return a node for display at this index. This will be called on the main thread and should - * not implement reuse (it will be called once per row). Unlike UICollectionView's version, - * this method is not called when the row is about to display. - */ -- (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index; - -/** - * This method replaces -collectionView:nodeBlockForItemAtIndexPath: - * This method takes precedence over pagerNode:nodeAtIndex: if implemented. - * - * @param pagerNode The sender. - * @param index The index of the requested node. - * @return a block that creates the node for display at this index. - * Must be thread-safe (can be called on the main thread or a background - * queue) and should not implement reuse (it will be called once per row). - */ -- (ASCellNodeBlock)pagerNode:(ASPagerNode *)pagerNode nodeBlockAtIndex:(NSInteger)index; - -@end - -@protocol ASPagerDelegate -@end - -/** - * A horizontal, paging collection node. - */ -@interface ASPagerNode : ASCollectionNode - -/** - * Configures a default horizontal, paging flow layout with 0 inter-item spacing. - */ -- (instancetype)init; - -/** - * Initializer with custom-configured flow layout properties. - * - * NOTE: The flow layout must have a horizontal scroll direction. - */ -- (instancetype)initWithCollectionViewLayout:(ASPagerFlowLayout *)flowLayout; - -/** - * Data Source is required, and uses a different protocol from ASCollectionNode. - */ -- (void)setDataSource:(nullable id )dataSource; -- (nullable id )dataSource AS_WARN_UNUSED_RESULT; - -/** - * Delegate is optional. - * This includes UIScrollViewDelegate as well as most methods from UICollectionViewDelegate, like willDisplay... - */ -- (void)setDelegate:(nullable id )delegate; -- (nullable id )delegate AS_WARN_UNUSED_RESULT; - -/** - * The underlying ASCollectionView object. - */ -@property (readonly) ASCollectionView *view; - -/** - * Returns the current page index. Main thread only. - */ -@property (nonatomic, readonly) NSInteger currentPageIndex; - -/** - * Scroll the contents of the receiver to ensure that the page is visible - */ -- (void)scrollToPageAtIndex:(NSInteger)index animated:(BOOL)animated; - -/** - * Returns the node for the passed page index - */ -- (ASCellNode *)nodeForPageAtIndex:(NSInteger)index AS_WARN_UNUSED_RESULT; - -/** - * Returns the index of the page for the cell passed or NSNotFound - */ -- (NSInteger)indexOfPageWithNode:(ASCellNode *)node; - -/** - * Tells the pager node to allow its view controller to automatically adjust its content insets. - * - * @see UIViewController.automaticallyAdjustsScrollViewInsets - * - * @discussion ASPagerNode should usually not have its content insets automatically adjusted - * because it scrolls horizontally, and flow layout will log errors because the pages - * do not fit between the top & bottom insets of the collection view. - * - * The default value is NO, which means that ASPagerNode expects that its view controller will - * have automaticallyAdjustsScrollViewInsets=NO. - * - * If this property is NO, but your view controller has automaticallyAdjustsScrollViewInsets=YES, - * the pager node will set the property on the view controller to NO and log a warning message. In the future, - * the pager node will just log the warning, and you'll need to configure your view controller on your own. - */ -@property (nonatomic) BOOL allowsAutomaticInsetsAdjustment; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASPagerNode.mm b/submodules/AsyncDisplayKit/Source/ASPagerNode.mm deleted file mode 100644 index 73c9987c68..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASPagerNode.mm +++ /dev/null @@ -1,234 +0,0 @@ -// -// ASPagerNode.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK -#import -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -@interface ASPagerNode () -{ - __weak id _pagerDataSource; - ASPagerNodeProxy *_proxyDataSource; - struct { - unsigned nodeBlockAtIndex:1; - unsigned nodeAtIndex:1; - } _pagerDataSourceFlags; - - __weak id _pagerDelegate; - ASPagerNodeProxy *_proxyDelegate; -} - -@end - -@implementation ASPagerNode - -@dynamic view, delegate, dataSource; - -#pragma mark - Lifecycle - -- (instancetype)init -{ - ASPagerFlowLayout *flowLayout = [[ASPagerFlowLayout alloc] init]; - flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal; - flowLayout.minimumInteritemSpacing = 0; - flowLayout.minimumLineSpacing = 0; - - return [self initWithCollectionViewLayout:flowLayout]; -} - -- (instancetype)initWithCollectionViewLayout:(ASPagerFlowLayout *)flowLayout; -{ - ASDisplayNodeAssert([flowLayout isKindOfClass:[ASPagerFlowLayout class]], @"ASPagerNode requires a flow layout."); - ASDisplayNodeAssertTrue(flowLayout.scrollDirection == UICollectionViewScrollDirectionHorizontal); - self = [super initWithCollectionViewLayout:flowLayout]; - return self; -} - -- (instancetype)initUsingAsyncCollectionLayout -{ - ASCollectionGalleryLayoutDelegate *layoutDelegate = [[ASCollectionGalleryLayoutDelegate alloc] initWithScrollableDirections:ASScrollDirectionHorizontalDirections]; - self = [super initWithLayoutDelegate:layoutDelegate layoutFacilitator:nil]; - if (self) { - layoutDelegate.propertiesProvider = self; - } - return self; -} - -#pragma mark - ASDisplayNode - -- (void)didLoad -{ - [super didLoad]; - - ASCollectionView *cv = self.view; - cv.asyncDataSource = (id)_proxyDataSource ?: self; - cv.asyncDelegate = (id)_proxyDelegate ?: self; -#if TARGET_OS_IOS - cv.pagingEnabled = YES; - cv.scrollsToTop = NO; -#endif - cv.allowsSelection = NO; - cv.showsVerticalScrollIndicator = NO; - cv.showsHorizontalScrollIndicator = NO; - - ASRangeTuningParameters minimumRenderParams = { .leadingBufferScreenfuls = 0.0, .trailingBufferScreenfuls = 0.0 }; - ASRangeTuningParameters minimumPreloadParams = { .leadingBufferScreenfuls = 1.0, .trailingBufferScreenfuls = 1.0 }; - [self setTuningParameters:minimumRenderParams forRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypeDisplay]; - [self setTuningParameters:minimumPreloadParams forRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypePreload]; - - ASRangeTuningParameters fullRenderParams = { .leadingBufferScreenfuls = 1.0, .trailingBufferScreenfuls = 1.0 }; - ASRangeTuningParameters fullPreloadParams = { .leadingBufferScreenfuls = 2.0, .trailingBufferScreenfuls = 2.0 }; - [self setTuningParameters:fullRenderParams forRangeMode:ASLayoutRangeModeFull rangeType:ASLayoutRangeTypeDisplay]; - [self setTuningParameters:fullPreloadParams forRangeMode:ASLayoutRangeModeFull rangeType:ASLayoutRangeTypePreload]; -} - -#pragma mark - Getters / Setters - -- (NSInteger)currentPageIndex -{ - return (self.view.contentOffset.x / [self pageSize].width); -} - -- (CGSize)pageSize -{ - UIEdgeInsets contentInset = self.contentInset; - CGSize pageSize = self.bounds.size; - pageSize.height -= (contentInset.top + contentInset.bottom); - return pageSize; -} - -#pragma mark - Helpers - -- (void)scrollToPageAtIndex:(NSInteger)index animated:(BOOL)animated -{ - NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0]; - [self scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionLeft animated:animated]; -} - -- (ASCellNode *)nodeForPageAtIndex:(NSInteger)index -{ - return [self nodeForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]]; -} - -- (NSInteger)indexOfPageWithNode:(ASCellNode *)node -{ - NSIndexPath *indexPath = [self indexPathForNode:node]; - if (!indexPath) { - return NSNotFound; - } - return indexPath.row; -} - -#pragma mark - ASCollectionGalleryLayoutPropertiesProviding - -- (CGSize)galleryLayoutDelegate:(nonnull ASCollectionGalleryLayoutDelegate *)delegate sizeForElements:(nonnull ASElementMap *)elements -{ - ASDisplayNodeAssertMainThread(); - return [self pageSize]; -} - -#pragma mark - ASCollectionDataSource - -- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath -{ - if (_pagerDataSourceFlags.nodeBlockAtIndex) { - return [_pagerDataSource pagerNode:self nodeBlockAtIndex:indexPath.item]; - } else if (_pagerDataSourceFlags.nodeAtIndex) { - ASCellNode *node = [_pagerDataSource pagerNode:self nodeAtIndex:indexPath.item]; - return ^{ return node; }; - } else { - ASDisplayNodeFailAssert(@"Pager data source must implement either %@ or %@. Data source: %@", NSStringFromSelector(@selector(pagerNode:nodeBlockAtIndex:)), NSStringFromSelector(@selector(pagerNode:nodeAtIndex:)), _pagerDataSource); - return ^{ - return [[ASCellNode alloc] init]; - }; - } -} - -- (NSInteger)collectionNode:(ASCollectionNode *)collectionNode numberOfItemsInSection:(NSInteger)section -{ - ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load nodes to display"); - return [_pagerDataSource numberOfPagesInPagerNode:self]; -} - -#pragma mark - ASCollectionDelegate - -- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode constrainedSizeForItemAtIndexPath:(NSIndexPath *)indexPath -{ - return ASSizeRangeMake([self pageSize]); -} - -#pragma mark - Data Source Proxy - -- (id )dataSource -{ - return _pagerDataSource; -} - -- (void)setDataSource:(id )dataSource -{ - if (dataSource != _pagerDataSource) { - _pagerDataSource = dataSource; - - if (dataSource == nil) { - memset(&_pagerDataSourceFlags, 0, sizeof(_pagerDataSourceFlags)); - } else { - _pagerDataSourceFlags.nodeBlockAtIndex = [_pagerDataSource respondsToSelector:@selector(pagerNode:nodeBlockAtIndex:)]; - _pagerDataSourceFlags.nodeAtIndex = [_pagerDataSource respondsToSelector:@selector(pagerNode:nodeAtIndex:)]; - } - - _proxyDataSource = dataSource ? [[ASPagerNodeProxy alloc] initWithTarget:dataSource interceptor:self] : nil; - - super.dataSource = (id )_proxyDataSource; - } -} - -- (void)setDelegate:(id)delegate -{ - if (delegate != _pagerDelegate) { - _pagerDelegate = delegate; - _proxyDelegate = delegate ? [[ASPagerNodeProxy alloc] initWithTarget:delegate interceptor:self] : nil; - super.delegate = (id )_proxyDelegate; - } -} - -- (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy -{ - [self setDataSource:nil]; - [self setDelegate:nil]; -} - -- (void)didEnterHierarchy -{ - [super didEnterHierarchy]; - - // Check that our view controller does not automatically set our content insets - // In every use case I can imagine, the pager is not hosted inside a range-managed node. - if (_allowsAutomaticInsetsAdjustment == NO) { - UIViewController *vc = [self.view asdk_associatedViewController]; - if (vc.automaticallyAdjustsScrollViewInsets) { - NSLog(@"AsyncDisplayKit: ASPagerNode is setting automaticallyAdjustsScrollViewInsets=NO on its owning view controller %@. This automatic behavior will be disabled in the future. Set allowsAutomaticInsetsAdjustment=YES on the pager node to suppress this behavior.", vc); - vc.automaticallyAdjustsScrollViewInsets = NO; - } - } -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Private/ASPendingStateController.h b/submodules/AsyncDisplayKit/Source/ASPendingStateController.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/Private/ASPendingStateController.h rename to submodules/AsyncDisplayKit/Source/ASPendingStateController.h diff --git a/submodules/AsyncDisplayKit/Source/Private/ASPendingStateController.mm b/submodules/AsyncDisplayKit/Source/ASPendingStateController.mm similarity index 91% rename from submodules/AsyncDisplayKit/Source/Private/ASPendingStateController.mm rename to submodules/AsyncDisplayKit/Source/ASPendingStateController.mm index 269b37e948..e7ca4a71de 100644 --- a/submodules/AsyncDisplayKit/Source/Private/ASPendingStateController.mm +++ b/submodules/AsyncDisplayKit/Source/ASPendingStateController.mm @@ -7,10 +7,10 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import +#import "ASPendingStateController.h" #import #import -#import // Required for -applyPendingViewState; consider moving this to +FrameworkPrivate +#import "ASDisplayNodeInternal.h" // Required for -applyPendingViewState; consider moving this to +FrameworkPrivate @interface ASPendingStateController() { diff --git a/submodules/AsyncDisplayKit/Source/ASRangeManagingNode.h b/submodules/AsyncDisplayKit/Source/ASRangeManagingNode.h deleted file mode 100644 index c331a77bee..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASRangeManagingNode.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// ASRangeManagingNode.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -@class ASCellNode; - -NS_ASSUME_NONNULL_BEGIN - -/** - * Basically ASTableNode or ASCollectionNode. - */ -@protocol ASRangeManagingNode - -/** - * Retrieve the index path for the given node, if it's a member of this container. - * - * @param node The node. - * @return The index path, or nil if the node is not part of this container. - */ -- (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)node; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Details/ASRecursiveUnfairLock.mm b/submodules/AsyncDisplayKit/Source/ASRecursiveUnfairLock.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/Details/ASRecursiveUnfairLock.mm rename to submodules/AsyncDisplayKit/Source/ASRecursiveUnfairLock.mm index 9e4a29d47a..e44eec76d1 100644 --- a/submodules/AsyncDisplayKit/Source/Details/ASRecursiveUnfairLock.mm +++ b/submodules/AsyncDisplayKit/Source/ASRecursiveUnfairLock.mm @@ -6,7 +6,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASRecursiveUnfairLock.h" +#import #import diff --git a/submodules/AsyncDisplayKit/Source/Private/ASResponderChainEnumerator.h b/submodules/AsyncDisplayKit/Source/ASResponderChainEnumerator.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/Private/ASResponderChainEnumerator.h rename to submodules/AsyncDisplayKit/Source/ASResponderChainEnumerator.h diff --git a/submodules/AsyncDisplayKit/Source/Private/ASResponderChainEnumerator.mm b/submodules/AsyncDisplayKit/Source/ASResponderChainEnumerator.mm similarity index 94% rename from submodules/AsyncDisplayKit/Source/Private/ASResponderChainEnumerator.mm rename to submodules/AsyncDisplayKit/Source/ASResponderChainEnumerator.mm index bb16e0fc57..2d94c99945 100644 --- a/submodules/AsyncDisplayKit/Source/Private/ASResponderChainEnumerator.mm +++ b/submodules/AsyncDisplayKit/Source/ASResponderChainEnumerator.mm @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import +#import "ASResponderChainEnumerator.h" #import @implementation ASResponderChainEnumerator { diff --git a/submodules/AsyncDisplayKit/Source/ASRunLoopQueue.mm b/submodules/AsyncDisplayKit/Source/ASRunLoopQueue.mm index 4aefeeb89d..2d2415e2b1 100644 --- a/submodules/AsyncDisplayKit/Source/ASRunLoopQueue.mm +++ b/submodules/AsyncDisplayKit/Source/ASRunLoopQueue.mm @@ -9,11 +9,10 @@ #import #import -#import #import #import #import -#import +#import "ASSignpost.h" #import #import #import @@ -115,9 +114,6 @@ static void runLoopSourceCallback(void *info) { NSPointerArray *_internalQueue; // Use NSPointerArray so we can decide __strong or __weak per-instance. AS::RecursiveMutex _internalQueueLock; - // In order to not pollute the top-level activities, each queue has 1 root activity. - os_activity_t _rootActivity; - #if ASRunLoopQueueLoggingEnabled NSTimer *_runloopQueueLoggingTimer; #endif @@ -138,15 +134,6 @@ static void runLoopSourceCallback(void *info) { _queueConsumer = handlerBlock; _batchSize = 1; _ensureExclusiveMembership = YES; - - // We don't want to pollute the top-level app activities with run loop batches, so we create one top-level - // activity per queue, and each batch activity joins that one instead. - _rootActivity = as_activity_create("Process run loop queue items", OS_ACTIVITY_NONE, OS_ACTIVITY_FLAG_DEFAULT); - { - // Log a message identifying this queue into the queue's root activity. - as_activity_scope_verbose(_rootActivity); - as_log_verbose(ASDisplayLog(), "Created run loop queue: %@", self); - } // Self is guaranteed to outlive the observer. Without the high cost of a weak pointer, // __unsafe_unretained allows us to avoid flagging the memory cycle detector. @@ -260,15 +247,10 @@ static void runLoopSourceCallback(void *info) { // itemsToProcess will be empty if _queueConsumer == nil so no need to check again. const auto count = itemsToProcess.size(); if (count > 0) { - as_activity_scope_verbose(as_activity_create("Process run loop queue batch", _rootActivity, OS_ACTIVITY_FLAG_DEFAULT)); const auto itemsEnd = itemsToProcess.cend(); for (auto iterator = itemsToProcess.begin(); iterator < itemsEnd; iterator++) { __unsafe_unretained id value = *iterator; _queueConsumer(value, isQueueDrained && iterator == itemsEnd - 1); - as_log_verbose(ASDisplayLog(), "processed %@", value); - } - if (count > 1) { - as_log_verbose(ASDisplayLog(), "processed %lu items", (unsigned long)count); } } @@ -339,7 +321,6 @@ ASSynthesizeLockingMethodsWithMutex(_internalQueueLock) AS::Mutex _internalQueueLock; // In order to not pollute the top-level activities, each queue has 1 root activity. - os_activity_t _rootActivity; #if ASRunLoopQueueLoggingEnabled NSTimer *_runloopQueueLoggingTimer; @@ -368,15 +349,6 @@ dispatch_once_t _ASSharedCATransactionQueueOnceToken; _internalQueue.reserve(kInternalQueueInitialCapacity); _batchBuffer.reserve(kInternalQueueInitialCapacity); - // We don't want to pollute the top-level app activities with run loop batches, so we create one top-level - // activity per queue, and each batch activity joins that one instead. - _rootActivity = as_activity_create("Process run loop queue items", OS_ACTIVITY_NONE, OS_ACTIVITY_FLAG_DEFAULT); - { - // Log a message identifying this queue into the queue's root activity. - as_activity_scope_verbose(_rootActivity); - as_log_verbose(ASDisplayLog(), "Created run loop queue: %@", self); - } - // Self is guaranteed to outlive the observer. Without the high cost of a weak pointer, // __unsafe_unretained allows us to avoid flagging the memory cycle detector. __unsafe_unretained __typeof__(self) weakSelf = self; @@ -439,7 +411,6 @@ dispatch_once_t _ASSharedCATransactionQueueOnceToken; if (count == 0) { return; } - as_activity_scope_verbose(as_activity_create("Process run loop queue batch", _rootActivity, OS_ACTIVITY_FLAG_DEFAULT)); ASSignpostStart(ASSignpostRunLoopQueueBatch); // Swap buffers, clear our hash table. @@ -451,10 +422,8 @@ dispatch_once_t _ASSharedCATransactionQueueOnceToken; for (const id &value : _batchBuffer) { [value prepareForCATransactionCommit]; - as_log_verbose(ASDisplayLog(), "processed %@", value); } _batchBuffer.clear(); - as_log_verbose(ASDisplayLog(), "processed %lu items", (unsigned long)count); ASSignpostEnd(ASSignpostRunLoopQueueBatch); } diff --git a/submodules/AsyncDisplayKit/Source/Details/ASScrollDirection.mm b/submodules/AsyncDisplayKit/Source/ASScrollDirection.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/Details/ASScrollDirection.mm rename to submodules/AsyncDisplayKit/Source/ASScrollDirection.mm diff --git a/submodules/AsyncDisplayKit/Source/ASScrollNode.mm b/submodules/AsyncDisplayKit/Source/ASScrollNode.mm index 4dc0652820..08fd82da80 100644 --- a/submodules/AsyncDisplayKit/Source/ASScrollNode.mm +++ b/submodules/AsyncDisplayKit/Source/ASScrollNode.mm @@ -15,7 +15,6 @@ #import #import #import -#import @interface ASScrollView : UIScrollView @end diff --git a/submodules/AsyncDisplayKit/Source/ASSectionController.h b/submodules/AsyncDisplayKit/Source/ASSectionController.h deleted file mode 100644 index 7df4dc84b8..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASSectionController.h +++ /dev/null @@ -1,88 +0,0 @@ -// -// ASSectionController.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@class ASBatchContext; - -/** - * A protocol that your section controllers should conform to, in order to be used with Texture. - * - * @note Your supplementary view source should conform to @c ASSupplementaryNodeSource. - */ -@protocol ASSectionController - -@optional - -/** - * A method to provide the node block for the item at the given index. - * The node block you return will be run asynchronously off the main thread, - * so it's important to retrieve any objects from your section _outside_ the block - * because by the time the block is run, the array may have changed. - * - * @param index The index of the item. - * @return A block to be run concurrently to build the node for this item. - * @see collectionNode:nodeBlockForItemAtIndexPath: - */ -- (ASCellNodeBlock)nodeBlockForItemAtIndex:(NSInteger)index; - -/** - * Similar to -collectionView:cellForItemAtIndexPath:. - * - * Note: only called if nodeBlockForItemAtIndex: returns nil. - * - * @param index The index of the item. - * - * @return A node to display for the given item. This will be called on the main thread and should - * not implement reuse (it will be called once per item). Unlike UICollectionView's version, - * this method is not called when the item is about to display. - */ -- (ASCellNode *)nodeForItemAtIndex:(NSInteger)index; - -/** - * Asks the section controller whether it should batch fetch because the user is - * near the end of the current data set. - * - * @discussion Use this method to conditionally fetch batches. Example use cases are: limiting the total number of - * objects that can be fetched or no network connection. - * - * If not implemented, the assumed return value is @c YES. - */ -- (BOOL)shouldBatchFetch; - -/** - * Asks the section controller to begin fetching more content (tail loading) because - * the user is near the end of the current data set. - * - * @param context A context object that must be notified when the batch fetch is completed. - * - * @discussion You must eventually call -completeBatchFetching: with an argument of YES in order to receive future - * notifications to do batch fetches. This method is called on a background queue. - */ -- (void)beginBatchFetchWithContext:(ASBatchContext *)context; - -/** - * A method to provide the size range used for measuring the item - * at the given index. - * - * @param index The index of the item. - * @return A size range used for asynchronously measuring the node at this index. - * @see collectionNode:constrainedSizeForItemAtIndexPath: - */ -- (ASSizeRange)sizeRangeForItemAtIndex:(NSInteger)index; - -@end - -NS_ASSUME_NONNULL_END -#endif diff --git a/submodules/AsyncDisplayKit/Source/Base/ASSignpost.h b/submodules/AsyncDisplayKit/Source/ASSignpost.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/Base/ASSignpost.h rename to submodules/AsyncDisplayKit/Source/ASSignpost.h diff --git a/submodules/AsyncDisplayKit/Source/ASSupplementaryNodeSource.h b/submodules/AsyncDisplayKit/Source/ASSupplementaryNodeSource.h deleted file mode 100644 index bc361dc736..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASSupplementaryNodeSource.h +++ /dev/null @@ -1,55 +0,0 @@ -// -// ASSupplementaryNodeSource.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@protocol ASSupplementaryNodeSource - -@optional - -/** - * A method to provide the node-block for the supplementary element. - * - * @param elementKind The kind of supplementary element. - * @param index The index of the item. - * @return A node block for the supplementary element. - * @see collectionNode:nodeForSupplementaryElementOfKind:atIndexPath: - */ -- (ASCellNodeBlock)nodeBlockForSupplementaryElementOfKind:(NSString *)elementKind atIndex:(NSInteger)index; - -/** - * Asks the controller to provide a node to display for the given supplementary element. - * - * @param kind The kind of supplementary element. - * @param index The index of the item. - */ -- (ASCellNode *)nodeForSupplementaryElementOfKind:(NSString *)kind atIndex:(NSInteger)index; - -/** - * A method to provide the size range used for measuring the supplementary - * element of the given kind at the given index. - * - * @param elementKind The kind of supplementary element. - * @param index The index of the item. - * @return A size range used for asynchronously measuring the node. - * @see collectionNode:constrainedSizeForSupplementaryElementOfKind:atIndexPath: - */ -- (ASSizeRange)sizeRangeForSupplementaryElementOfKind:(NSString *)elementKind atIndex:(NSInteger)index; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASTabBarController.h b/submodules/AsyncDisplayKit/Source/ASTabBarController.h deleted file mode 100644 index 7479f7ac85..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTabBarController.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// ASTabBarController.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * ASTabBarController - * - * @discussion ASTabBarController is a drop in replacement for UITabBarController - * which implements the memory efficiency improving @c ASManagesChildVisibilityDepth protocol. - * - * @see ASManagesChildVisibilityDepth - */ -@interface ASTabBarController : UITabBarController - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASTabBarController.mm b/submodules/AsyncDisplayKit/Source/ASTabBarController.mm deleted file mode 100644 index c0f9e39dd3..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTabBarController.mm +++ /dev/null @@ -1,87 +0,0 @@ -// -// ASTabBarController.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK -#import -#import - -@implementation ASTabBarController -{ - BOOL _parentManagesVisibilityDepth; - NSInteger _visibilityDepth; -} - -ASVisibilityDidMoveToParentViewController; - -ASVisibilityViewWillAppear; - -ASVisibilityViewDidDisappearImplementation; - -ASVisibilitySetVisibilityDepth; - -ASVisibilityDepthImplementation; - -- (void)visibilityDepthDidChange -{ - for (UIViewController *viewController in self.viewControllers) { - if ([viewController conformsToProtocol:@protocol(ASVisibilityDepth)]) { - [(id )viewController visibilityDepthDidChange]; - } - } -} - -- (NSInteger)visibilityDepthOfChildViewController:(UIViewController *)childViewController -{ - NSUInteger viewControllerIndex = [self.viewControllers indexOfObjectIdenticalTo:childViewController]; - if (viewControllerIndex == NSNotFound) { - //If childViewController is not actually a child, return NSNotFound which is also a really large number. - return NSNotFound; - } - - if (self.selectedViewController == childViewController) { - return [self visibilityDepth]; - } - return [self visibilityDepth] + 1; -} - -#pragma mark - UIKit overrides - -- (void)setViewControllers:(NSArray<__kindof UIViewController *> *)viewControllers -{ - [super setViewControllers:viewControllers]; - [self visibilityDepthDidChange]; -} - -- (void)setViewControllers:(NSArray<__kindof UIViewController *> *)viewControllers animated:(BOOL)animated -{ - [super setViewControllers:viewControllers animated:animated]; - [self visibilityDepthDidChange]; -} - -- (void)setSelectedIndex:(NSUInteger)selectedIndex -{ - as_activity_create_for_scope("Set selected index of ASTabBarController"); - as_log_info(ASNodeLog(), "Selected tab %tu of %@", selectedIndex, self); - - [super setSelectedIndex:selectedIndex]; - [self visibilityDepthDidChange]; -} - -- (void)setSelectedViewController:(__kindof UIViewController *)selectedViewController -{ - as_activity_create_for_scope("Set selected view controller of ASTabBarController"); - as_log_info(ASNodeLog(), "Selected view controller %@ of %@", selectedViewController, self); - - [super setSelectedViewController:selectedViewController]; - [self visibilityDepthDidChange]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASTableNode+Beta.h b/submodules/AsyncDisplayKit/Source/ASTableNode+Beta.h deleted file mode 100644 index 6d580bdc43..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTableNode+Beta.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// ASTableNode+Beta.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@protocol ASBatchFetchingDelegate; - -NS_ASSUME_NONNULL_BEGIN - -@interface ASTableNode (Beta) - -@property (nonatomic, weak) id batchFetchingDelegate; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASTableNode.h b/submodules/AsyncDisplayKit/Source/ASTableNode.h deleted file mode 100644 index f05c9f1dc8..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTableNode.h +++ /dev/null @@ -1,759 +0,0 @@ -// -// ASTableNode.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// -#ifndef MINIMAL_ASDK -#import -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@protocol ASTableDataSource; -@protocol ASTableDelegate; -@class ASTableView, ASBatchContext; - -/** - * ASTableNode is a node based class that wraps an ASTableView. It can be used - * as a subnode of another node, and provide room for many (great) features and improvements later on. - */ -@interface ASTableNode : ASDisplayNode - -- (instancetype)init; // UITableViewStylePlain -- (instancetype)initWithStyle:(UITableViewStyle)style NS_DESIGNATED_INITIALIZER; - -@property (readonly) ASTableView *view; - -// These properties can be set without triggering the view to be created, so it's fine to set them in -init. -@property (nonatomic, weak) id delegate; -@property (nonatomic, weak) id dataSource; - -/** - * The number of screens left to scroll before the delegate -tableNode:beginBatchFetchingWithContext: is called. - * - * Defaults to two screenfuls. - */ -@property (nonatomic) CGFloat leadingScreensForBatching; - -/* - * A Boolean value that determines whether the table will be flipped. - * If the value of this property is YES, the first cell node will be at the bottom of the table (as opposed to the top by default). This is useful for chat/messaging apps. The default value is NO. - */ -@property (nonatomic) BOOL inverted; - -/** - * The distance that the content view is inset from the table node edges. Defaults to UIEdgeInsetsZero. - */ -@property (nonatomic) UIEdgeInsets contentInset; - -/** - * The offset of the content view's origin from the table node's origin. Defaults to CGPointZero. - */ -@property (nonatomic) CGPoint contentOffset; - -/** - * Sets the offset from the content node’s origin to the table node’s origin. - * - * @param contentOffset The offset - * - * @param animated YES to animate to this new offset at a constant velocity, NO to not aniamte and immediately make the transition. - */ -- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated; - -/** - * YES to automatically adjust the contentOffset when cells are inserted or deleted above - * visible cells, maintaining the users' visible scroll position. - * - * @note This is only applied to non-animated updates. For animated updates, there is no way to - * synchronize or "cancel out" the appearance of a scroll due to UITableView API limitations. - * - * default is NO. - */ -@property (nonatomic) BOOL automaticallyAdjustsContentOffset; - -/* - * A Boolean value that determines whether users can select a row. - * If the value of this property is YES (the default), users can select rows. If you set it to NO, they cannot select rows. Setting this property affects cell selection only when the table view is not in editing mode. If you want to restrict selection of cells in editing mode, use `allowsSelectionDuringEditing`. - */ -@property (nonatomic) BOOL allowsSelection; -/* - * A Boolean value that determines whether users can select cells while the table view is in editing mode. - * If the value of this property is YES, users can select rows during editing. The default value is NO. If you want to restrict selection of cells regardless of mode, use allowsSelection. - */ -@property (nonatomic) BOOL allowsSelectionDuringEditing; -/* - * A Boolean value that determines whether users can select more than one row outside of editing mode. - * This property controls whether multiple rows can be selected simultaneously outside of editing mode. When the value of this property is YES, each row that is tapped acquires a selected appearance. Tapping the row again removes the selected appearance. If you access indexPathsForSelectedRows, you can get the index paths that identify the selected rows. - */ -@property (nonatomic) BOOL allowsMultipleSelection; -/* - * A Boolean value that controls whether users can select more than one cell simultaneously in editing mode. - * The default value of this property is NO. If you set it to YES, check marks appear next to selected rows in editing mode. In addition, UITableView does not query for editing styles when it goes into editing mode. If you access indexPathsForSelectedRows, you can get the index paths that identify the selected rows. - */ -@property (nonatomic) BOOL allowsMultipleSelectionDuringEditing; - -/** - * Tuning parameters for a range type in full mode. - * - * @param rangeType The range type to get the tuning parameters for. - * - * @return A tuning parameter value for the given range type in full mode. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType AS_WARN_UNUSED_RESULT; - -/** - * Set the tuning parameters for a range type in full mode. - * - * @param tuningParameters The tuning parameters to store for a range type. - * @param rangeType The range type to set the tuning parameters for. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType; - -/** - * Tuning parameters for a range type in the specified mode. - * - * @param rangeMode The range mode to get the running parameters for. - * @param rangeType The range type to get the tuning parameters for. - * - * @return A tuning parameter value for the given range type in the given mode. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType AS_WARN_UNUSED_RESULT; - -/** - * Set the tuning parameters for a range type in the specified mode. - * - * @param tuningParameters The tuning parameters to store for a range type. - * @param rangeMode The range mode to set the running parameters for. - * @param rangeType The range type to set the tuning parameters for. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; - -/** - * Scrolls the table to the given row. - * - * @param indexPath The index path of the row. - * @param scrollPosition Where the row should end up after the scroll. - * @param animated Whether the scroll should be animated or not. - * - * This method must be called on the main thread. - */ -- (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated; - -/** - * Reload everything from scratch, destroying the working range and all cached nodes. - * - * @param completion block to run on completion of asynchronous loading or nil. If supplied, the block is run on - * the main thread. - * @warning This method is substantially more expensive than UITableView's version. - */ -- (void)reloadDataWithCompletion:(nullable void (^)(void))completion; - -/** - * Reload everything from scratch, destroying the working range and all cached nodes. - * - * @warning This method is substantially more expensive than UITableView's version. - */ -- (void)reloadData; - -/** - * Triggers a relayout of all nodes. - * - * @discussion This method invalidates and lays out every cell node in the table view. - */ -- (void)relayoutItems; - -/** - * Perform a batch of updates asynchronously, optionally disabling all animations in the batch. This method must be called from the main thread. - * The data source must be updated to reflect the changes before the update block completes. - * - * @param animated NO to disable animations for this batch - * @param updates The block that performs the relevant insert, delete, reload, or move operations. - * @param completion A completion handler block to execute when all of the operations are finished. This block takes a single - * Boolean parameter that contains the value YES if all of the related animations completed successfully or - * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. - */ -- (void)performBatchAnimated:(BOOL)animated updates:(nullable AS_NOESCAPE void (^)(void))updates completion:(nullable void (^)(BOOL finished))completion; - -/** - * Perform a batch of updates asynchronously with animations in the batch. This method must be called from the main thread. - * The data source must be updated to reflect the changes before the update block completes. - * - * @param updates The block that performs the relevant insert, delete, reload, or move operations. - * @param completion A completion handler block to execute when all of the operations are finished. This block takes a single - * Boolean parameter that contains the value YES if all of the related animations completed successfully or - * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. - */ -- (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)(void))updates completion:(nullable void (^)(BOOL finished))completion; - -/** - * Returns YES if the ASCollectionNode is still processing changes from performBatchUpdates:. - * This is typically the concurrent allocation (calling nodeBlocks) and layout of newly inserted - * ASCellNodes. If YES is returned, then calling -waitUntilAllUpdatesAreProcessed may take tens of - * milliseconds to return as it blocks on these concurrent operations. - * - * Returns NO if ASCollectionNode is fully synchronized with the underlying UICollectionView. This - * means that until the next performBatchUpdates: is called, it is safe to compare UIKit values - * (such as from UICollectionViewLayout) with your app's data source. - * - * This method will always return NO if called immediately after -waitUntilAllUpdatesAreProcessed. - */ -@property (nonatomic, readonly) BOOL isProcessingUpdates; - -/** - * Schedules a block to be performed (on the main thread) after processing of performBatchUpdates: - * is finished (completely synchronized to UIKit). The blocks will be run at the moment that - * -isProcessingUpdates changes from YES to NO; - * - * When isProcessingUpdates == NO, the block is run block immediately (before the method returns). - * - * Blocks scheduled by this mechanism are NOT guaranteed to run in the order they are scheduled. - * They may also be delayed if performBatchUpdates continues to be called; the blocks will wait until - * all running updates are finished. - * - * Calling -waitUntilAllUpdatesAreProcessed is one way to flush any pending update completion blocks. - */ -- (void)onDidFinishProcessingUpdates:(void (^)(void))didFinishProcessingUpdates; - -/** - * Blocks execution of the main thread until all section and item updates are committed to the view. This method must be called from the main thread. - */ -- (void)waitUntilAllUpdatesAreProcessed; - -/** - * Inserts one or more sections, with an option to animate the insertion. - * - * @param sections An index set that specifies the sections to insert. - * - * @param animation A constant that indicates how the insertion is to be animated. See UITableViewRowAnimation. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation; - -/** - * Deletes one or more sections, with an option to animate the deletion. - * - * @param sections An index set that specifies the sections to delete. - * - * @param animation A constant that indicates how the deletion is to be animated. See UITableViewRowAnimation. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation; - -/** - * Reloads the specified sections using a given animation effect. - * - * @param sections An index set that specifies the sections to reload. - * - * @param animation A constant that indicates how the reloading is to be animated. See UITableViewRowAnimation. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation; - -/** - * Moves a section to a new location. - * - * @param section The index of the section to move. - * - * @param newSection The index that is the destination of the move for the section. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection; - -/** - * Inserts rows at the locations identified by an array of index paths, with an option to animate the insertion. - * - * @param indexPaths An array of NSIndexPath objects, each representing a row index and section index that together identify a row. - * - * @param animation A constant that indicates how the insertion is to be animated. See UITableViewRowAnimation. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation; - -/** - * Deletes the rows specified by an array of index paths, with an option to animate the deletion. - * - * @param indexPaths An array of NSIndexPath objects identifying the rows to delete. - * - * @param animation A constant that indicates how the deletion is to be animated. See UITableViewRowAnimation. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation; - -/** - * Reloads the specified rows using a given animation effect. - * - * @param indexPaths An array of NSIndexPath objects identifying the rows to reload. - * - * @param animation A constant that indicates how the reloading is to be animated. See UITableViewRowAnimation. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation; - -/** - * Moves the row at a specified location to a destination location. - * - * @param indexPath The index path identifying the row to move. - * - * @param newIndexPath The index path that is the destination of the move for the row. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath; - -#pragma mark - Selection - -/** - * Selects a row in the table view identified by index path, optionally scrolling the row to a location in the table view. - * This method does not cause any selection-related delegate methods to be called. - * - * @param indexPath An index path identifying a row in the table view. - * - * @param animated Specify YES to animate the change in the selection or NO to make the change without animating it. - * - * @param scrollPosition A constant that identifies a relative position in the table view (top, middle, bottom) for the row when scrolling concludes. See `UITableViewScrollPosition` for descriptions of valid constants. - * - * @discussion This method must be called from the main thread. - */ -- (void)selectRowAtIndexPath:(nullable NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition; - -/* - * Deselects a given row identified by index path, with an option to animate the deselection. - * This method does not cause any selection-related delegate methods to be called. - * Calling this method does not cause any scrolling to the deselected row. - * - * @param indexPath An index path identifying a row in the table view. - * - * @param animated Specify YES to animate the change in the selection or NO to make the change without animating it. - * - * @discussion This method must be called from the main thread. - */ -- (void)deselectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated; - -#pragma mark - Querying Data - -/** - * Retrieves the number of rows in the given section. - * - * @param section The section. - * - * @return The number of rows. - */ -- (NSInteger)numberOfRowsInSection:(NSInteger)section AS_WARN_UNUSED_RESULT; - -/** - * The number of sections in the table node. - */ -@property (nonatomic, readonly) NSInteger numberOfSections; - -/** - * Similar to -visibleCells. - * - * @return an array containing the nodes being displayed on screen. This must be called on the main thread. - */ -@property (nonatomic, readonly) NSArray<__kindof ASCellNode *> *visibleNodes; - -/** - * Retrieves the node for the row at the given index path. - */ -- (nullable __kindof ASCellNode *)nodeForRowAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT; - -/** - * Similar to -indexPathForCell:. - * - * @param cellNode a node for a row. - * - * @return The index path to this row, if it exists. - * - * @discussion This method will return @c nil for a node that is still being - * displayed in the table view, if the data source has deleted the row. - * That is, the node is visible but it no longer corresponds - * to any item in the data source and will be removed soon. - */ -- (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode AS_WARN_UNUSED_RESULT; - -/** - * Similar to -[UITableView rectForRowAtIndexPath:] - * - * @param indexPath An index path identifying a row in the table view. - * - * @return A rectangle defining the area in which the table view draws the row or CGRectZero if indexPath is invalid. - * - * @discussion This method must be called from the main thread. - */ -- (CGRect)rectForRowAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT; - -/** - * Similar to -[UITableView cellForRowAtIndexPath:] - * - * @param indexPath An index path identifying a row in the table view. - * - * @return An object representing a cell of the table, or nil if the cell is not visible or indexPath is out of range. - * - * @discussion This method must be called from the main thread. - */ -- (nullable __kindof UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT; - -/** - * Similar to UITableView.indexPathForSelectedRow - * - * @return The value of this property is an index path identifying the row and section - * indexes of the selected row, or nil if the index path is invalid. If there are multiple selections, - * this property contains the first index-path object in the array of row selections; - * this object has the lowest index values for section and row. - * - * @discussion This method must be called from the main thread. - */ -@property (nullable, nonatomic, copy, readonly) NSIndexPath *indexPathForSelectedRow; - -@property (nonatomic, readonly, nullable) NSArray *indexPathsForSelectedRows; - -/** - * Similar to -[UITableView indexPathForRowAtPoint:] - * - * @param point A point in the local coordinate system of the table view (the table view’€™s bounds). - * - * @return An index path representing the row and section associated with point, - * or nil if the point is out of the bounds of any row. - * - * @discussion This method must be called from the main thread. - */ -- (nullable NSIndexPath *)indexPathForRowAtPoint:(CGPoint)point AS_WARN_UNUSED_RESULT; - -/** - * Similar to -[UITableView indexPathsForRowsInRect:] - * - * @param rect A rectangle defining an area of the table view in local coordinates. - * - * @return An array of NSIndexPath objects each representing a row and section index identifying a row within rect. - * Returns an empty array if there aren’t any rows to return. - * - * @discussion This method must be called from the main thread. - */ -- (nullable NSArray *)indexPathsForRowsInRect:(CGRect)rect AS_WARN_UNUSED_RESULT; - -/** - * Similar to -[UITableView indexPathsForVisibleRows] - * - * @return The value of this property is an array of NSIndexPath objects each representing a row index and section index - * that together identify a visible row in the table view. If no rows are visible, the value is nil. - * - * @discussion This method must be called from the main thread. - */ -- (NSArray *)indexPathsForVisibleRows AS_WARN_UNUSED_RESULT; - -@end - -/** - * This is a node-based UITableViewDataSource. - */ -@protocol ASTableDataSource - -@optional - -/** - * Asks the data source for the number of sections in the table node. - * - * @see @c numberOfSectionsInTableView: - */ -- (NSInteger)numberOfSectionsInTableNode:(ASTableNode *)tableNode; - -/** - * Asks the data source for the number of rows in the given section of the table node. - * - * @see @c numberOfSectionsInTableView: - */ -- (NSInteger)tableNode:(ASTableNode *)tableNode numberOfRowsInSection:(NSInteger)section; - -/** - * Asks the data source for a block to create a node to represent the row at the given index path. - * The block will be run by the table node concurrently in the background before the row is inserted - * into the table view. - * - * @param tableNode The sender. - * @param indexPath The index path of the row. - * - * @return a block that creates the node for display at this indexpath. - * Must be thread-safe (can be called on the main thread or a background - * queue) and should not implement reuse (it will be called once per row). - * - * @note This method takes precedence over tableNode:nodeForRowAtIndexPath: if implemented. - */ -- (ASCellNodeBlock)tableNode:(ASTableNode *)tableNode nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath; - -/** - * Asks the data source for a node to represent the row at the given index path. - * - * @param tableNode The sender. - * @param indexPath The index path of the row. - * - * @return a node to display for this row. This will be called on the main thread and should not implement reuse (it will be called once per row). Unlike UITableView's version, this method - * is not called when the row is about to display. - */ -- (ASCellNode *)tableNode:(ASTableNode *)tableNode nodeForRowAtIndexPath:(NSIndexPath *)indexPath; - -/** - * Similar to -tableView:cellForRowAtIndexPath:. - * - * @param tableView The sender. - * - * @param indexPath The index path of the requested node. - * - * @return a node for display at this indexpath. This will be called on the main thread and should not implement reuse (it will be called once per row). Unlike UITableView's version, this method - * is not called when the row is about to display. - */ -- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's method instead."); - -/** - * Similar to -tableView:nodeForRowAtIndexPath: - * This method takes precedence over tableView:nodeForRowAtIndexPath: if implemented. - * @param tableView The sender. - * - * @param indexPath The index path of the requested node. - * - * @return a block that creates the node for display at this indexpath. - * Must be thread-safe (can be called on the main thread or a background - * queue) and should not implement reuse (it will be called once per row). - */ -- (ASCellNodeBlock)tableView:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's method instead."); - -/** - * Indicator to lock the data source for data fetching in async mode. - * We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistency or exception - * due to the data access in async mode. - * - * @param tableView The sender. - * @deprecated The data source is always accessed on the main thread, and this method will not be called. - */ -- (void)tableViewLockDataSource:(ASTableView *)tableView ASDISPLAYNODE_DEPRECATED_MSG("Data source accesses are on the main thread. Method will not be called."); - -/** - * Indicator to unlock the data source for data fetching in asyn mode. - * We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistency or exception - * due to the data access in async mode. - * - * @param tableView The sender. - * @deprecated The data source is always accessed on the main thread, and this method will not be called. - */ -- (void)tableViewUnlockDataSource:(ASTableView *)tableView ASDISPLAYNODE_DEPRECATED_MSG("Data source accesses are on the main thread. Method will not be called."); - -/** - * Generate a unique identifier for an element in a table. This helps state restoration persist the scroll position - * of a table view even when the data in that table changes. See the documentation for UIDataSourceModelAssociation for more information. - * - * @param indexPath The index path of the requested node. - * - * @param tableNode The sender. - * - * @return a unique identifier for the element at the given path. Return nil if the index path does not exist in the table. - */ -- (nullable NSString *)modelIdentifierForElementAtIndexPath:(NSIndexPath *)indexPath inNode:(ASTableNode *)tableNode; - -/** - * Similar to -tableView:cellForRowAtIndexPath:. See the documentation for UIDataSourceModelAssociation for more information. - * - * @param identifier The model identifier of the element, previously generated by a call to modelIdentifierForElementAtIndexPath. - * - * @param tableNode The sender. - * - * @return the index path to the current position of the matching element in the table. Return nil if the element is not found. - */ -- (nullable NSIndexPath *)indexPathForElementWithModelIdentifier:(NSString *)identifier inNode:(ASTableNode *)tableNode; - -@end - -/** - * This is a node-based UITableViewDelegate. - * - * Note that -tableView:heightForRowAtIndexPath: has been removed; instead, your custom ASCellNode subclasses are - * responsible for deciding their preferred onscreen height in -calculateSizeThatFits:. - */ -@protocol ASTableDelegate - -@optional - -- (void)tableNode:(ASTableNode *)tableNode willDisplayRowWithNode:(ASCellNode *)node; - -- (void)tableNode:(ASTableNode *)tableNode didEndDisplayingRowWithNode:(ASCellNode *)node; - -- (nullable NSIndexPath *)tableNode:(ASTableNode *)tableNode willSelectRowAtIndexPath:(NSIndexPath *)indexPath; - -- (void)tableNode:(ASTableNode *)tableNode didSelectRowAtIndexPath:(NSIndexPath *)indexPath; - -- (nullable NSIndexPath *)tableNode:(ASTableNode *)tableNode willDeselectRowAtIndexPath:(NSIndexPath *)indexPath; - -- (void)tableNode:(ASTableNode *)tableNode didDeselectRowAtIndexPath:(NSIndexPath *)indexPath; - -- (BOOL)tableNode:(ASTableNode *)tableNode shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath; -- (void)tableNode:(ASTableNode *)tableNode didHighlightRowAtIndexPath:(NSIndexPath *)indexPath; -- (void)tableNode:(ASTableNode *)tableNode didUnhighlightRowAtIndexPath:(NSIndexPath *)indexPath; - -- (BOOL)tableNode:(ASTableNode *)tableNode shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath; -- (BOOL)tableNode:(ASTableNode *)tableNode canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender; -- (void)tableNode:(ASTableNode *)tableNode performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender; - -/** - * Provides the constrained size range for measuring the row at the index path. - * Note: the widths in the returned size range are ignored! - * - * @param tableNode The sender. - * - * @param indexPath The index path of the node. - * - * @return A constrained size range for layout the node at this index path. - */ -- (ASSizeRange)tableNode:(ASTableNode *)tableNode constrainedSizeForRowAtIndexPath:(NSIndexPath *)indexPath; - -/** - * Receive a message that the tableView is near the end of its data set and more data should be fetched if necessary. - * - * @param tableNode The sender. - * @param context A context object that must be notified when the batch fetch is completed. - * - * @discussion You must eventually call -completeBatchFetching: with an argument of YES in order to receive future - * notifications to do batch fetches. This method is called on a background queue. - * - * ASTableView currently only supports batch events for tail loads. If you require a head load, consider implementing a - * UIRefreshControl. - */ -- (void)tableNode:(ASTableNode *)tableNode willBeginBatchFetchWithContext:(ASBatchContext *)context; - -/** - * Tell the tableView if batch fetching should begin. - * - * @param tableNode The sender. - * - * @discussion Use this method to conditionally fetch batches. Example use cases are: limiting the total number of - * objects that can be fetched or no network connection. - * - * If not implemented, the tableView assumes that it should notify its asyncDelegate when batch fetching - * should occur. - */ -- (BOOL)shouldBatchFetchForTableNode:(ASTableNode *)tableNode; - -/** - * Informs the delegate that the table view will add the given node - * at the given index path to the view hierarchy. - * - * @param tableView The sender. - * @param node The node that will be displayed. - * @param indexPath The index path of the row that will be displayed. - * - * @warning AsyncDisplayKit processes table view edits asynchronously. The index path - * passed into this method may not correspond to the same item in your data source - * if your data source has been updated since the last edit was processed. - */ -- (void)tableView:(ASTableView *)tableView willDisplayNode:(ASCellNode *)node forRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's method instead."); - -/** - * Informs the delegate that the table view did remove the provided node from the view hierarchy. - * This may be caused by the node scrolling out of view, or by deleting the row - * or its containing section with @c deleteRowsAtIndexPaths:withRowAnimation: or @c deleteSections:withRowAnimation: . - * - * @param tableView The sender. - * @param node The node which was removed from the view hierarchy. - * @param indexPath The index path at which the node was located before the removal. - * - * @warning AsyncDisplayKit processes table view edits asynchronously. The index path - * passed into this method may not correspond to the same item in your data source - * if your data source has been updated since the last edit was processed. - */ -- (void)tableView:(ASTableView *)tableView didEndDisplayingNode:(ASCellNode *)node forRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's method instead."); - -/** - * Receive a message that the tableView is near the end of its data set and more data should be fetched if necessary. - * - * @param tableView The sender. - * @param context A context object that must be notified when the batch fetch is completed. - * - * @discussion You must eventually call -completeBatchFetching: with an argument of YES in order to receive future - * notifications to do batch fetches. This method is called on a background queue. - * - * ASTableView currently only supports batch events for tail loads. If you require a head load, consider implementing a - * UIRefreshControl. - */ -- (void)tableView:(ASTableView *)tableView willBeginBatchFetchWithContext:(ASBatchContext *)context ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's method instead."); - -/** - * Tell the tableView if batch fetching should begin. - * - * @param tableView The sender. - * - * @discussion Use this method to conditionally fetch batches. Example use cases are: limiting the total number of - * objects that can be fetched or no network connection. - * - * If not implemented, the tableView assumes that it should notify its asyncDelegate when batch fetching - * should occur. - */ -- (BOOL)shouldBatchFetchForTableView:(ASTableView *)tableView AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's method instead."); - -/** - * Provides the constrained size range for measuring the row at the index path. - * Note: the widths in the returned size range are ignored! - * - * @param tableView The sender. - * - * @param indexPath The index path of the node. - * - * @return A constrained size range for layout the node at this index path. - */ -- (ASSizeRange)tableView:(ASTableView *)tableView constrainedSizeForRowAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's method instead."); - -/** - * Informs the delegate that the table view will add the node - * at the given index path to the view hierarchy. - * - * @param tableView The sender. - * @param indexPath The index path of the row that will be displayed. - * - * @warning AsyncDisplayKit processes table view edits asynchronously. The index path - * passed into this method may not correspond to the same item in your data source - * if your data source has been updated since the last edit was processed. - * - * This method is deprecated. Use @c tableView:willDisplayNode:forRowAtIndexPath: instead. - */ -- (void)tableView:(ASTableView *)tableView willDisplayNodeForRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's method instead."); - -@end - -@interface ASTableNode (Deprecated) - -- (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("This method has been renamed to -waitUntilAllUpdatesAreProcessed."); - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASTableNode.mm b/submodules/AsyncDisplayKit/Source/ASTableNode.mm deleted file mode 100644 index b1ce6a6274..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTableNode.mm +++ /dev/null @@ -1,871 +0,0 @@ -// -// ASTableNode.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK -#import -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#pragma mark - _ASTablePendingState - -@interface _ASTablePendingState : NSObject { -@public - std::vector> _tuningParameters; -} -@property (nonatomic, weak) id delegate; -@property (nonatomic, weak) id dataSource; -@property (nonatomic) ASLayoutRangeMode rangeMode; -@property (nonatomic) BOOL allowsSelection; -@property (nonatomic) BOOL allowsSelectionDuringEditing; -@property (nonatomic) BOOL allowsMultipleSelection; -@property (nonatomic) BOOL allowsMultipleSelectionDuringEditing; -@property (nonatomic) BOOL inverted; -@property (nonatomic) CGFloat leadingScreensForBatching; -@property (nonatomic) UIEdgeInsets contentInset; -@property (nonatomic) CGPoint contentOffset; -@property (nonatomic) BOOL animatesContentOffset; -@property (nonatomic) BOOL automaticallyAdjustsContentOffset; - -@end - -@implementation _ASTablePendingState - -#pragma mark - Lifecycle - -- (instancetype)init -{ - self = [super init]; - if (self) { - _rangeMode = ASLayoutRangeModeUnspecified; - _tuningParameters = [ASAbstractLayoutController defaultTuningParameters]; - _allowsSelection = YES; - _allowsSelectionDuringEditing = NO; - _allowsMultipleSelection = NO; - _allowsMultipleSelectionDuringEditing = NO; - _inverted = NO; - _leadingScreensForBatching = 2; - _contentInset = UIEdgeInsetsZero; - _contentOffset = CGPointZero; - _animatesContentOffset = NO; - _automaticallyAdjustsContentOffset = NO; - } - return self; -} - -#pragma mark Tuning Parameters - -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType -{ - return [self tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; -} - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType -{ - return [self setTuningParameters:tuningParameters forRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; -} - -- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - ASDisplayNodeAssert(rangeMode < _tuningParameters.size() && rangeType < _tuningParameters[rangeMode].size(), @"Requesting a range that is OOB for the configured tuning parameters"); - return _tuningParameters[rangeMode][rangeType]; -} - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - ASDisplayNodeAssert(rangeMode < _tuningParameters.size() && rangeType < _tuningParameters[rangeMode].size(), @"Setting a range that is OOB for the configured tuning parameters"); - _tuningParameters[rangeMode][rangeType] = tuningParameters; -} - -@end - -#pragma mark - ASTableView - -@interface ASTableNode () -{ - AS::RecursiveMutex _environmentStateLock; - id _batchFetchingDelegate; -} - -@property (nonatomic) _ASTablePendingState *pendingState; -@property (nonatomic, weak) ASRangeController *rangeController; -@end - -@implementation ASTableNode - -#pragma mark Lifecycle - -- (instancetype)initWithStyle:(UITableViewStyle)style -{ - if (self = [super init]) { - __weak __typeof__(self) weakSelf = self; - [self setViewBlock:^{ - // Variable will be unused if event logging is off. - __unused __typeof__(self) strongSelf = weakSelf; - return [[ASTableView alloc] _initWithFrame:CGRectZero style:style dataControllerClass:nil owningNode:strongSelf eventLog:ASDisplayNodeGetEventLog(strongSelf)]; - }]; - } - return self; -} - -- (instancetype)init -{ - return [self initWithStyle:UITableViewStylePlain]; -} - -#if ASDISPLAYNODE_ASSERTIONS_ENABLED -- (void)dealloc -{ - if (self.nodeLoaded) { - __weak UIView *view = self.view; - ASPerformBlockOnMainThread(^{ - ASDisplayNodeCAssertNil(view.superview, @"Node's view should be removed from hierarchy."); - }); - } -} -#endif - -#pragma mark ASDisplayNode - -- (void)didLoad -{ - [super didLoad]; - - ASTableView *view = self.view; - view.tableNode = self; - - _rangeController = view.rangeController; - - if (_pendingState) { - _ASTablePendingState *pendingState = _pendingState; - self.pendingState = nil; - view.asyncDelegate = pendingState.delegate; - view.asyncDataSource = pendingState.dataSource; - view.inverted = pendingState.inverted; - view.allowsSelection = pendingState.allowsSelection; - view.allowsSelectionDuringEditing = pendingState.allowsSelectionDuringEditing; - view.allowsMultipleSelection = pendingState.allowsMultipleSelection; - view.allowsMultipleSelectionDuringEditing = pendingState.allowsMultipleSelectionDuringEditing; - view.automaticallyAdjustsContentOffset = pendingState.automaticallyAdjustsContentOffset; - - UIEdgeInsets contentInset = pendingState.contentInset; - if (!UIEdgeInsetsEqualToEdgeInsets(contentInset, UIEdgeInsetsZero)) { - view.contentInset = contentInset; - } - - CGPoint contentOffset = pendingState.contentOffset; - if (!CGPointEqualToPoint(contentOffset, CGPointZero)) { - [view setContentOffset:contentOffset animated:pendingState.animatesContentOffset]; - } - - const auto tuningParametersVector = pendingState->_tuningParameters; - const auto tuningParametersVectorSize = tuningParametersVector.size(); - for (NSInteger rangeMode = 0; rangeMode < tuningParametersVectorSize; rangeMode++) { - const auto tuningparametersRangeModeVector = tuningParametersVector[rangeMode]; - const auto tuningParametersVectorRangeModeSize = tuningparametersRangeModeVector.size(); - for (NSInteger rangeType = 0; rangeType < tuningParametersVectorRangeModeSize; rangeType++) { - ASRangeTuningParameters tuningParameters = tuningparametersRangeModeVector[rangeType]; - [_rangeController setTuningParameters:tuningParameters - forRangeMode:(ASLayoutRangeMode)rangeMode - rangeType:(ASLayoutRangeType)rangeType]; - } - } - - if (pendingState.rangeMode != ASLayoutRangeModeUnspecified) { - [_rangeController updateCurrentRangeWithMode:pendingState.rangeMode]; - } - } -} - -- (ASTableView *)view -{ - return (ASTableView *)[super view]; -} - -- (void)clearContents -{ - [super clearContents]; - [self.rangeController clearContents]; -} - -- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState -{ - [super interfaceStateDidChange:newState fromState:oldState]; - [ASRangeController layoutDebugOverlayIfNeeded]; -} - -- (void)didEnterPreloadState -{ - [super didEnterPreloadState]; - // Intentionally allocate the view here and trigger a layout pass on it, which in turn will trigger the intial data load. - // We can get rid of this call later when ASDataController, ASRangeController and ASCollectionLayout can operate without the view. - [self.view layoutIfNeeded]; -} - -#if ASRangeControllerLoggingEnabled -- (void)didEnterVisibleState -{ - [super didEnterVisibleState]; - NSLog(@"%@ - visible: YES", self); -} - -- (void)didExitVisibleState -{ - [super didExitVisibleState]; - NSLog(@"%@ - visible: NO", self); -} -#endif - -- (void)didExitPreloadState -{ - [super didExitPreloadState]; - [self.rangeController clearPreloadedData]; -} - -#pragma mark Setter / Getter - -// TODO: Implement this without the view. Then revisit ASLayoutElementCollectionTableSetTraitCollection -- (ASDataController *)dataController -{ - return self.view.dataController; -} - -- (_ASTablePendingState *)pendingState -{ - if (!_pendingState && ![self isNodeLoaded]) { - _pendingState = [[_ASTablePendingState alloc] init]; - } - ASDisplayNodeAssert(![self isNodeLoaded] || !_pendingState, @"ASTableNode should not have a pendingState once it is loaded"); - return _pendingState; -} - -- (void)setInverted:(BOOL)inverted -{ - self.transform = inverted ? CATransform3DMakeScale(1, -1, 1) : CATransform3DIdentity; - if ([self pendingState]) { - _pendingState.inverted = inverted; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist"); - self.view.inverted = inverted; - } -} - -- (BOOL)inverted -{ - if ([self pendingState]) { - return _pendingState.inverted; - } else { - return self.view.inverted; - } -} - -- (void)setLeadingScreensForBatching:(CGFloat)leadingScreensForBatching -{ - _ASTablePendingState *pendingState = self.pendingState; - if (pendingState) { - pendingState.leadingScreensForBatching = leadingScreensForBatching; - } else { - ASDisplayNodeAssert(self.nodeLoaded, @"ASTableNode should be loaded if pendingState doesn't exist"); - self.view.leadingScreensForBatching = leadingScreensForBatching; - } -} - -- (CGFloat)leadingScreensForBatching -{ - _ASTablePendingState *pendingState = self.pendingState; - if (pendingState) { - return pendingState.leadingScreensForBatching; - } else { - return self.view.leadingScreensForBatching; - } -} - -- (void)setContentInset:(UIEdgeInsets)contentInset -{ - _ASTablePendingState *pendingState = self.pendingState; - if (pendingState) { - pendingState.contentInset = contentInset; - } else { - ASDisplayNodeAssert(self.nodeLoaded, @"ASTableNode should be loaded if pendingState doesn't exist"); - self.view.contentInset = contentInset; - } -} - -- (UIEdgeInsets)contentInset -{ - _ASTablePendingState *pendingState = self.pendingState; - if (pendingState) { - return pendingState.contentInset; - } else { - return self.view.contentInset; - } -} - -- (void)setContentOffset:(CGPoint)contentOffset -{ - [self setContentOffset:contentOffset animated:NO]; -} - -- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated -{ - _ASTablePendingState *pendingState = self.pendingState; - if (pendingState) { - pendingState.contentOffset = contentOffset; - pendingState.animatesContentOffset = animated; - } else { - ASDisplayNodeAssert(self.nodeLoaded, @"ASTableNode should be loaded if pendingState doesn't exist"); - [self.view setContentOffset:contentOffset animated:animated]; - } -} - -- (CGPoint)contentOffset -{ - _ASTablePendingState *pendingState = self.pendingState; - if (pendingState) { - return pendingState.contentOffset; - } else { - return self.view.contentOffset; - } -} - -- (void)setAutomaticallyAdjustsContentOffset:(BOOL)automaticallyAdjustsContentOffset -{ - _ASTablePendingState *pendingState = self.pendingState; - if (pendingState) { - pendingState.automaticallyAdjustsContentOffset = automaticallyAdjustsContentOffset; - } else { - ASDisplayNodeAssert(self.nodeLoaded, @"ASTableNode should be loaded if pendingState doesn't exist"); - self.view.automaticallyAdjustsContentOffset = automaticallyAdjustsContentOffset; - } -} - -- (BOOL)automaticallyAdjustsContentOffset -{ - _ASTablePendingState *pendingState = self.pendingState; - if (pendingState) { - return pendingState.automaticallyAdjustsContentOffset; - } else { - return self.view.automaticallyAdjustsContentOffset; - } -} - -- (void)setDelegate:(id )delegate -{ - if ([self pendingState]) { - _pendingState.delegate = delegate; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist"); - - // Manually trampoline to the main thread. The view requires this be called on main - // and asserting here isn't an option – it is a common pattern for users to clear - // the delegate/dataSource in dealloc, which may be running on a background thread. - // It is important that we avoid retaining self in this block, so that this method is dealloc-safe. - ASTableView *view = self.view; - ASPerformBlockOnMainThread(^{ - view.asyncDelegate = delegate; - }); - } -} - -- (id )delegate -{ - if ([self pendingState]) { - return _pendingState.delegate; - } else { - return self.view.asyncDelegate; - } -} - -- (void)setDataSource:(id )dataSource -{ - if ([self pendingState]) { - _pendingState.dataSource = dataSource; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist"); - - // Manually trampoline to the main thread. The view requires this be called on main - // and asserting here isn't an option – it is a common pattern for users to clear - // the delegate/dataSource in dealloc, which may be running on a background thread. - // It is important that we avoid retaining self in this block, so that this method is dealloc-safe. - ASTableView *view = self.view; - ASPerformBlockOnMainThread(^{ - view.asyncDataSource = dataSource; - }); - } -} - -- (id )dataSource -{ - if ([self pendingState]) { - return _pendingState.dataSource; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist"); - return self.view.asyncDataSource; - } -} - -- (void)setAllowsSelection:(BOOL)allowsSelection -{ - if ([self pendingState]) { - _pendingState.allowsSelection = allowsSelection; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist"); - self.view.allowsSelection = allowsSelection; - } -} - -- (BOOL)allowsSelection -{ - if ([self pendingState]) { - return _pendingState.allowsSelection; - } else { - return self.view.allowsSelection; - } -} - -- (void)setAllowsSelectionDuringEditing:(BOOL)allowsSelectionDuringEditing -{ - if ([self pendingState]) { - _pendingState.allowsSelectionDuringEditing = allowsSelectionDuringEditing; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist"); - self.view.allowsSelectionDuringEditing = allowsSelectionDuringEditing; - } -} - -- (BOOL)allowsSelectionDuringEditing -{ - if ([self pendingState]) { - return _pendingState.allowsSelectionDuringEditing; - } else { - return self.view.allowsSelectionDuringEditing; - } -} - -- (void)setAllowsMultipleSelection:(BOOL)allowsMultipleSelection -{ - if ([self pendingState]) { - _pendingState.allowsMultipleSelection = allowsMultipleSelection; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist"); - self.view.allowsMultipleSelection = allowsMultipleSelection; - } -} - -- (BOOL)allowsMultipleSelection -{ - if ([self pendingState]) { - return _pendingState.allowsMultipleSelection; - } else { - return self.view.allowsMultipleSelection; - } -} - -- (void)setAllowsMultipleSelectionDuringEditing:(BOOL)allowsMultipleSelectionDuringEditing -{ - if ([self pendingState]) { - _pendingState.allowsMultipleSelectionDuringEditing = allowsMultipleSelectionDuringEditing; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist"); - self.view.allowsMultipleSelectionDuringEditing = allowsMultipleSelectionDuringEditing; - } -} - -- (BOOL)allowsMultipleSelectionDuringEditing -{ - if ([self pendingState]) { - return _pendingState.allowsMultipleSelectionDuringEditing; - } else { - return self.view.allowsMultipleSelectionDuringEditing; - } -} - -- (void)setBatchFetchingDelegate:(id)batchFetchingDelegate -{ - _batchFetchingDelegate = batchFetchingDelegate; -} - -- (id)batchFetchingDelegate -{ - return _batchFetchingDelegate; -} - -#pragma mark ASRangeControllerUpdateRangeProtocol - -- (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode -{ - if ([self pendingState]) { - _pendingState.rangeMode = rangeMode; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist"); - [self.rangeController updateCurrentRangeWithMode:rangeMode]; - } -} - -#pragma mark ASEnvironment - -ASLayoutElementCollectionTableSetTraitCollection(_environmentStateLock) - -#pragma mark - Range Tuning - -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType -{ - return [self tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; -} - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType -{ - [self setTuningParameters:tuningParameters forRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; -} - -- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - if ([self pendingState]) { - return [_pendingState tuningParametersForRangeMode:rangeMode rangeType:rangeType]; - } else { - return [self.rangeController tuningParametersForRangeMode:rangeMode rangeType:rangeType]; - } -} - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - if ([self pendingState]) { - [_pendingState setTuningParameters:tuningParameters forRangeMode:rangeMode rangeType:rangeType]; - } else { - return [self.rangeController setTuningParameters:tuningParameters forRangeMode:rangeMode rangeType:rangeType]; - } -} - -#pragma mark - Selection - -- (void)selectRowAtIndexPath:(nullable NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition -{ - ASDisplayNodeAssertMainThread(); - ASTableView *tableView = self.view; - - indexPath = [tableView convertIndexPathFromTableNode:indexPath waitingIfNeeded:YES]; - if (indexPath != nil) { - [tableView selectRowAtIndexPath:indexPath animated:animated scrollPosition:scrollPosition]; - } else { - NSLog(@"Failed to select row at index path %@ because the row never reached the view.", indexPath); - } - -} - -- (void)deselectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated -{ - ASDisplayNodeAssertMainThread(); - ASTableView *tableView = self.view; - - indexPath = [tableView convertIndexPathFromTableNode:indexPath waitingIfNeeded:YES]; - if (indexPath != nil) { - [tableView deselectRowAtIndexPath:indexPath animated:animated]; - } else { - NSLog(@"Failed to deselect row at index path %@ because the row never reached the view.", indexPath); - } -} - -- (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated -{ - ASDisplayNodeAssertMainThread(); - ASTableView *tableView = self.view; - - indexPath = [tableView convertIndexPathFromTableNode:indexPath waitingIfNeeded:YES]; - - if (indexPath != nil) { - [tableView scrollToRowAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated]; - } else { - NSLog(@"Failed to scroll to row at index path %@ because the row never reached the view.", indexPath); - } -} - -#pragma mark - Querying Data - -- (void)reloadDataInitiallyIfNeeded -{ - ASDisplayNodeAssertMainThread(); - if (!self.dataController.initialReloadDataHasBeenCalled) { - // Note: Just calling reloadData isn't enough here – we need to - // ensure that _nodesConstrainedWidth is updated first. - [self.view layoutIfNeeded]; - } -} - -- (NSInteger)numberOfRowsInSection:(NSInteger)section -{ - ASDisplayNodeAssertMainThread(); - [self reloadDataInitiallyIfNeeded]; - return [self.dataController.pendingMap numberOfItemsInSection:section]; -} - -- (NSInteger)numberOfSections -{ - ASDisplayNodeAssertMainThread(); - [self reloadDataInitiallyIfNeeded]; - return [self.dataController.pendingMap numberOfSections]; -} - -- (NSArray<__kindof ASCellNode *> *)visibleNodes -{ - ASDisplayNodeAssertMainThread(); - return self.isNodeLoaded ? [self.view visibleNodes] : @[]; -} - -- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode -{ - return [self.dataController.pendingMap indexPathForElement:cellNode.collectionElement]; -} - -- (ASCellNode *)nodeForRowAtIndexPath:(NSIndexPath *)indexPath -{ - [self reloadDataInitiallyIfNeeded]; - return [self.dataController.pendingMap elementForItemAtIndexPath:indexPath].node; -} - -- (CGRect)rectForRowAtIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertMainThread(); - ASTableView *tableView = self.view; - - indexPath = [tableView convertIndexPathFromTableNode:indexPath waitingIfNeeded:YES]; - return [tableView rectForRowAtIndexPath:indexPath]; -} - -- (nullable __kindof UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertMainThread(); - ASTableView *tableView = self.view; - - indexPath = [tableView convertIndexPathFromTableNode:indexPath waitingIfNeeded:YES]; - if (indexPath == nil) { - return nil; - } - return [tableView cellForRowAtIndexPath:indexPath]; -} - -- (nullable NSIndexPath *)indexPathForSelectedRow -{ - ASDisplayNodeAssertMainThread(); - ASTableView *tableView = self.view; - - NSIndexPath *indexPath = tableView.indexPathForSelectedRow; - if (indexPath != nil) { - return [tableView convertIndexPathToTableNode:indexPath]; - } - return indexPath; -} - -- (NSArray *)indexPathsForSelectedRows -{ - ASDisplayNodeAssertMainThread(); - ASTableView *tableView = self.view; - - return [tableView convertIndexPathsToTableNode:tableView.indexPathsForSelectedRows]; -} - -- (nullable NSIndexPath *)indexPathForRowAtPoint:(CGPoint)point -{ - ASDisplayNodeAssertMainThread(); - ASTableView *tableView = self.view; - - NSIndexPath *indexPath = [tableView indexPathForRowAtPoint:point]; - if (indexPath != nil) { - return [tableView convertIndexPathToTableNode:indexPath]; - } - return indexPath; -} - -- (nullable NSArray *)indexPathsForRowsInRect:(CGRect)rect -{ - ASDisplayNodeAssertMainThread(); - ASTableView *tableView = self.view; - return [tableView convertIndexPathsToTableNode:[tableView indexPathsForRowsInRect:rect]]; -} - -- (NSArray *)indexPathsForVisibleRows -{ - ASDisplayNodeAssertMainThread(); - NSMutableArray *indexPathsArray = [NSMutableArray new]; - for (ASCellNode *cell in [self visibleNodes]) { - NSIndexPath *indexPath = [self indexPathForNode:cell]; - if (indexPath) { - [indexPathsArray addObject:indexPath]; - } - } - return indexPathsArray; -} - -#pragma mark - Editing - -- (void)reloadDataWithCompletion:(void (^)())completion -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view reloadDataWithCompletion:completion]; - } else { - if (completion) { - completion(); - } - } -} - -- (void)reloadData -{ - [self reloadDataWithCompletion:nil]; -} - -- (void)relayoutItems -{ - [self.view relayoutItems]; -} - -- (void)performBatchAnimated:(BOOL)animated updates:(NS_NOESCAPE void (^)())updates completion:(void (^)(BOOL))completion -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - ASTableView *tableView = self.view; - [tableView beginUpdates]; - if (updates) { - updates(); - } - [tableView endUpdatesAnimated:animated completion:completion]; - } else { - if (updates) { - updates(); - } - } -} - -- (void)performBatchUpdates:(NS_NOESCAPE void (^)())updates completion:(void (^)(BOOL))completion -{ - [self performBatchAnimated:YES updates:updates completion:completion]; -} - -- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view insertSections:sections withRowAnimation:animation]; - } -} - -- (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view deleteSections:sections withRowAnimation:animation]; - } -} - -- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view reloadSections:sections withRowAnimation:animation]; - } -} - -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view moveSection:section toSection:newSection]; - } -} - -- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view insertRowsAtIndexPaths:indexPaths withRowAnimation:animation]; - } -} - -- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view deleteRowsAtIndexPaths:indexPaths withRowAnimation:animation]; - } -} - -- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view reloadRowsAtIndexPaths:indexPaths withRowAnimation:animation]; - } -} - -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view moveRowAtIndexPath:indexPath toIndexPath:newIndexPath]; - } -} - -- (BOOL)isProcessingUpdates -{ - return (self.nodeLoaded ? [self.view isProcessingUpdates] : NO); -} - -- (void)onDidFinishProcessingUpdates:(void (^)())completion -{ - if (!completion) { - return; - } - if (!self.nodeLoaded) { - completion(); - } else { - [self.view onDidFinishProcessingUpdates:completion]; - } -} - -- (void)waitUntilAllUpdatesAreProcessed -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view waitUntilAllUpdatesAreCommitted]; - } -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (void)waitUntilAllUpdatesAreCommitted -{ - [self waitUntilAllUpdatesAreProcessed]; -} -#pragma clang diagnostic pop - -#pragma mark - Debugging (Private) - -- (NSMutableArray *)propertiesForDebugDescription -{ - NSMutableArray *result = [super propertiesForDebugDescription]; - [result addObject:@{ @"dataSource" : ASObjectDescriptionMakeTiny(self.dataSource) }]; - [result addObject:@{ @"delegate" : ASObjectDescriptionMakeTiny(self.delegate) }]; - return result; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASTableView.h b/submodules/AsyncDisplayKit/Source/ASTableView.h deleted file mode 100644 index 3f627beb11..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTableView.h +++ /dev/null @@ -1,249 +0,0 @@ -// -// ASTableView.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@class ASCellNode; -@protocol ASTableDataSource; -@protocol ASTableDelegate; -@class ASTableNode; - -/** - * Asynchronous UITableView with Intelligent Preloading capabilities. - * - * @note ASTableNode is strongly recommended over ASTableView. This class is provided for adoption convenience. - */ -@interface ASTableView : UITableView - -/// The corresponding table node, or nil if one does not exist. -@property (nonatomic, weak, readonly) ASTableNode *tableNode; - -/** - * Retrieves the node for the row at the given index path. - */ -- (nullable ASCellNode *)nodeForRowAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT; - -@end - -@interface ASTableView (Deprecated) - -@property (nonatomic, weak) id asyncDelegate ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's .delegate property instead."); -@property (nonatomic, weak) id asyncDataSource ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode .dataSource property instead."); - -/** - * Initializer. - * - * @param frame A rectangle specifying the initial location and size of the table view in its superview’€™s coordinates. - * The frame of the table view changes as table cells are added and deleted. - * - * @param style A constant that specifies the style of the table view. See UITableViewStyle for descriptions of valid constants. - */ -- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style ASDISPLAYNODE_DEPRECATED_MSG("Please use ASTableNode instead of ASTableView."); - -/** - * The number of screens left to scroll before the delegate -tableView:beginBatchFetchingWithContext: is called. - * - * Defaults to two screenfuls. - */ -@property (nonatomic) CGFloat leadingScreensForBatching ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead."); - -/** - * The distance that the content view is inset from the table view edges. Defaults to UIEdgeInsetsZero. - */ -@property (nonatomic) UIEdgeInsets contentInset ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead"); - -/** - * The offset of the content view's origin from the table node's origin. Defaults to CGPointZero. - */ -@property (nonatomic) CGPoint contentOffset ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead."); - -/** - * YES to automatically adjust the contentOffset when cells are inserted or deleted above - * visible cells, maintaining the users' visible scroll position. - * - * @note This is only applied to non-animated updates. For animated updates, there is no way to - * synchronize or "cancel out" the appearance of a scroll due to UITableView API limitations. - * - * default is NO. - */ -@property (nonatomic) BOOL automaticallyAdjustsContentOffset ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead."); - -/* - * A Boolean value that determines whether the nodes that the data source renders will be flipped. - */ -@property (nonatomic) BOOL inverted ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead."); - -@property (nonatomic, readonly, nullable) NSIndexPath *indexPathForSelectedRow ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead."); - -@property (nonatomic, readonly, nullable) NSArray *indexPathsForSelectedRows ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead."); - -@property (nonatomic, readonly, nullable) NSArray *indexPathsForVisibleRows ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead."); - -/** - * Tuning parameters for a range type in full mode. - * - * @param rangeType The range type to get the tuning parameters for. - * - * @return A tuning parameter value for the given range type in full mode. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -/** - * Set the tuning parameters for a range type in full mode. - * - * @param tuningParameters The tuning parameters to store for a range type. - * @param rangeType The range type to set the tuning parameters for. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -/** - * Tuning parameters for a range type in the specified mode. - * - * @param rangeMode The range mode to get the running parameters for. - * @param rangeType The range type to get the tuning parameters for. - * - * @return A tuning parameter value for the given range type in the given mode. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -/** - * Set the tuning parameters for a range type in the specified mode. - * - * @param tuningParameters The tuning parameters to store for a range type. - * @param rangeMode The range mode to set the running parameters for. - * @param rangeType The range type to set the tuning parameters for. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (nullable __kindof UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)selectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (nullable NSIndexPath *)indexPathForRowAtPoint:(CGPoint)point ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (nullable NSArray *)indexPathsForRowsInRect:(CGRect)rect ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -/** - * Similar to -visibleCells. - * - * @return an array containing the cell nodes being displayed on screen. - */ -- (NSArray *)visibleNodes AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -/** - * Similar to -indexPathForCell:. - * - * @param cellNode a cellNode part of the table view - * - * @return an indexPath for this cellNode - */ -- (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -/** - * Reload everything from scratch, destroying the working range and all cached nodes. - * - * @param completion block to run on completion of asynchronous loading or nil. If supplied, the block is run on - * the main thread. - * @warning This method is substantially more expensive than UITableView's version. - */ --(void)reloadDataWithCompletion:(void (^ _Nullable)(void))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -/** - * Reload everything from scratch, destroying the working range and all cached nodes. - * - * @warning This method is substantially more expensive than UITableView's version. - */ -- (void)reloadData ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -/** - * Triggers a relayout of all nodes. - * - * @discussion This method invalidates and lays out every cell node in the table view. - */ -- (void)relayoutItems ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)beginUpdates ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's -performBatchUpdates:completion: instead."); - -- (void)endUpdates ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's -performBatchUpdates:completion: instead."); - -/** - * Concludes a series of method calls that insert, delete, select, or reload rows and sections of the table view. - * You call this method to bracket a series of method calls that begins with beginUpdates and that consists of operations - * to insert, delete, select, and reload rows and sections of the table view. When you call endUpdates, ASTableView begins animating - * the operations simultaneously. This method is must be called from the main thread. It's important to remember that the ASTableView will - * be processing the updates asynchronously after this call and are not guaranteed to be reflected in the ASTableView until - * the completion block is executed. - * - * @param animated NO to disable all animations. - * @param completion A completion handler block to execute when all of the operations are finished. This block takes a single - * Boolean parameter that contains the value YES if all of the related animations completed successfully or - * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. - */ -- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^ _Nullable)(BOOL completed))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's -performBatchUpdates:completion: instead."); - -/** - * See ASTableNode.h for full documentation of these methods. - */ -@property (nonatomic, readonly) BOOL isProcessingUpdates; -- (void)onDidFinishProcessingUpdates:(void (^)(void))completion; -- (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("Use -[ASTableNode waitUntilAllUpdatesAreProcessed] instead."); - -- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -@end - -ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASTableDataSource.") -@protocol ASTableViewDataSource -@end - -ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASTableDelegate.") -@protocol ASTableViewDelegate -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASTableView.mm b/submodules/AsyncDisplayKit/Source/ASTableView.mm deleted file mode 100644 index 170abb50b5..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTableView.mm +++ /dev/null @@ -1,2044 +0,0 @@ -// -// ASTableView.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - -//#define LOG(...) NSLog(__VA_ARGS__) -#define LOG(...) - -/** - * See note at the top of ASCollectionView.mm near declaration of macro GET_COLLECTIONNODE_OR_RETURN - */ -#define GET_TABLENODE_OR_RETURN(__var, __val) \ - ASTableNode *__var = self.tableNode; \ - if (__var == nil) { \ - return __val; \ - } - -#define UITABLEVIEW_RESPONDS_TO_SELECTOR() \ - ({ \ - static BOOL superResponds; \ - static dispatch_once_t onceToken; \ - dispatch_once(&onceToken, ^{ \ - superResponds = [UITableView instancesRespondToSelector:_cmd]; \ - }); \ - superResponds; \ - }) - -@interface UITableView (ScrollViewDelegate) - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView; -- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView; -- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView; -- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset; -- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate; - -@end - -#pragma mark - -#pragma mark ASCellNode<->UITableViewCell bridging. - -@class _ASTableViewCell; - -@protocol _ASTableViewCellDelegate -- (void)didLayoutSubviewsOfTableViewCell:(_ASTableViewCell *)tableViewCell; -@end - -@interface _ASTableViewCell : UITableViewCell -@property (nonatomic, weak) id<_ASTableViewCellDelegate> delegate; -@property (nonatomic, readonly) ASCellNode *node; -@property (nonatomic) ASCollectionElement *element; -@end - -@implementation _ASTableViewCell -// TODO add assertions to prevent use of view-backed UITableViewCell properties (eg .textLabel) - -- (void)layoutSubviews -{ - [super layoutSubviews]; - [_delegate didLayoutSubviewsOfTableViewCell:self]; -} - -- (void)didTransitionToState:(UITableViewCellStateMask)state -{ - [self setNeedsLayout]; - [self layoutIfNeeded]; - [super didTransitionToState:state]; -} - -- (ASCellNode *)node -{ - return self.element.node; -} - -- (void)setElement:(ASCollectionElement *)element -{ - _element = element; - ASCellNode *node = element.node; - - if (node) { - self.backgroundColor = node.backgroundColor; - self.selectedBackgroundView = node.selectedBackgroundView; - self.backgroundView = node.backgroundView; -#if TARGET_OS_IOS - self.separatorInset = node.separatorInset; -#endif - self.selectionStyle = node.selectionStyle; - self.focusStyle = node.focusStyle; - self.accessoryType = node.accessoryType; - self.tintColor = node.tintColor; - - // the following ensures that we clip the entire cell to it's bounds if node.clipsToBounds is set (the default) - // This is actually a workaround for a bug we are seeing in some rare cases (selected background view - // overlaps other cells if size of ASCellNode has changed.) - self.clipsToBounds = node.clipsToBounds; - } - - [node __setSelectedFromUIKit:self.selected]; - [node __setHighlightedFromUIKit:self.highlighted]; -} - -- (BOOL)consumesCellNodeVisibilityEvents -{ - ASCellNode *node = self.node; - if (node == nil) { - return NO; - } - return ASSubclassOverridesSelector([ASCellNode class], [node class], @selector(cellNodeVisibilityEvent:inScrollView:withCellFrame:)); -} - -- (void)setSelected:(BOOL)selected animated:(BOOL)animated -{ - [super setSelected:selected animated:animated]; - [self.node __setSelectedFromUIKit:selected]; -} - -- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated -{ - [super setHighlighted:highlighted animated:animated]; - [self.node __setHighlightedFromUIKit:highlighted]; -} - -- (void)prepareForReuse -{ - // Need to clear element before UIKit calls setSelected:NO / setHighlighted:NO on its cells - self.element = nil; - [super prepareForReuse]; -} - -@end - -#pragma mark - -#pragma mark ASTableView - -@interface ASTableView () -{ - ASTableViewProxy *_proxyDataSource; - ASTableViewProxy *_proxyDelegate; - - ASTableLayoutController *_layoutController; - - ASRangeController *_rangeController; - - ASBatchContext *_batchContext; - - // When we update our data controller in response to an interactive move, - // we don't want to tell the table view about the change (it knows!) - BOOL _updatingInResponseToInteractiveMove; - BOOL _inverted; - - // The top cell node that was visible before the update. - __weak ASCellNode *_contentOffsetAdjustmentTopVisibleNode; - // The y-offset of the top visible row's origin before the update. - CGFloat _contentOffsetAdjustmentTopVisibleNodeOffset; - CGFloat _leadingScreensForBatching; - BOOL _automaticallyAdjustsContentOffset; - - CGPoint _deceleratingVelocity; - - CGFloat _nodesConstrainedWidth; - BOOL _queuedNodeHeightUpdate; - BOOL _isDeallocating; - NSHashTable<_ASTableViewCell *> *_cellsForVisibilityUpdates; - - // CountedSet because UIKit may display the same element in multiple cells e.g. during animations. - NSCountedSet *_visibleElements; - - NSHashTable *_cellsForLayoutUpdates; - - // See documentation on same property in ASCollectionView - BOOL _hasEverCheckedForBatchFetchingDueToUpdate; - - // The section index overlay view, if there is one present. - // This is useful because we need to measure our row nodes against (width - indexView.width). - __weak UIView *_sectionIndexView; - - /** - * The change set that we're currently building, if any. - */ - _ASHierarchyChangeSet *_changeSet; - - /** - * Counter used to keep track of nested batch updates. - */ - NSInteger _batchUpdateCount; - - /** - * Keep a strong reference to node till view is ready to release. - */ - ASTableNode *_keepalive_node; - - struct { - unsigned int scrollViewDidScroll:1; - unsigned int scrollViewWillBeginDragging:1; - unsigned int scrollViewDidEndDragging:1; - unsigned int scrollViewWillEndDragging:1; - unsigned int scrollViewDidEndDecelerating:1; - unsigned int tableNodeWillDisplayNodeForRow:1; - unsigned int tableViewWillDisplayNodeForRow:1; - unsigned int tableViewWillDisplayNodeForRowDeprecated:1; - unsigned int tableNodeDidEndDisplayingNodeForRow:1; - unsigned int tableViewDidEndDisplayingNodeForRow:1; - unsigned int tableNodeWillBeginBatchFetch:1; - unsigned int tableViewWillBeginBatchFetch:1; - unsigned int shouldBatchFetchForTableView:1; - unsigned int shouldBatchFetchForTableNode:1; - unsigned int tableViewConstrainedSizeForRow:1; - unsigned int tableNodeConstrainedSizeForRow:1; - unsigned int tableViewWillSelectRow:1; - unsigned int tableNodeWillSelectRow:1; - unsigned int tableViewDidSelectRow:1; - unsigned int tableNodeDidSelectRow:1; - unsigned int tableViewWillDeselectRow:1; - unsigned int tableNodeWillDeselectRow:1; - unsigned int tableViewDidDeselectRow:1; - unsigned int tableNodeDidDeselectRow:1; - unsigned int tableViewShouldHighlightRow:1; - unsigned int tableNodeShouldHighlightRow:1; - unsigned int tableViewDidHighlightRow:1; - unsigned int tableNodeDidHighlightRow:1; - unsigned int tableViewDidUnhighlightRow:1; - unsigned int tableNodeDidUnhighlightRow:1; - unsigned int tableViewShouldShowMenuForRow:1; - unsigned int tableNodeShouldShowMenuForRow:1; - unsigned int tableViewCanPerformActionForRow:1; - unsigned int tableNodeCanPerformActionForRow:1; - unsigned int tableViewPerformActionForRow:1; - unsigned int tableNodePerformActionForRow:1; - } _asyncDelegateFlags; - - struct { - unsigned int numberOfSectionsInTableView:1; - unsigned int numberOfSectionsInTableNode:1; - unsigned int tableNodeNumberOfRowsInSection:1; - unsigned int tableViewNumberOfRowsInSection:1; - unsigned int tableViewNodeBlockForRow:1; - unsigned int tableNodeNodeBlockForRow:1; - unsigned int tableViewNodeForRow:1; - unsigned int tableNodeNodeForRow:1; - unsigned int tableViewCanMoveRow:1; - unsigned int tableNodeCanMoveRow:1; - unsigned int tableViewMoveRow:1; - unsigned int tableNodeMoveRow:1; - unsigned int sectionIndexMethods:1; // if both section index methods are implemented - unsigned int modelIdentifierMethods:1; // if both modelIdentifierForElementAtIndexPath and indexPathForElementWithModelIdentifier are implemented - } _asyncDataSourceFlags; -} - -@property (nonatomic) ASDataController *dataController; - -@property (nonatomic, weak) ASTableNode *tableNode; - -@property (nonatomic) BOOL test_enableSuperUpdateCallLogging; -@end - -@implementation ASTableView -{ - __weak id _asyncDelegate; - __weak id _asyncDataSource; -} - -// Using _ASDisplayLayer ensures things like -layout are properly forwarded to ASTableNode. -+ (Class)layerClass -{ - return [_ASDisplayLayer class]; -} - -+ (Class)dataControllerClass -{ - return [ASDataController class]; -} - -#pragma mark - -#pragma mark Lifecycle - -- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style -{ - return [self _initWithFrame:frame style:style dataControllerClass:nil owningNode:nil eventLog:nil]; -} - -- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass owningNode:(ASTableNode *)tableNode eventLog:(ASEventLog *)eventLog -{ - if (!(self = [super initWithFrame:frame style:style])) { - return nil; - } - _cellsForVisibilityUpdates = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality]; - _cellsForLayoutUpdates = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality]; - if (!dataControllerClass) { - dataControllerClass = [[self class] dataControllerClass]; - } - - _layoutController = [[ASTableLayoutController alloc] initWithTableView:self]; - - _rangeController = [[ASRangeController alloc] init]; - _rangeController.layoutController = _layoutController; - _rangeController.dataSource = self; - _rangeController.delegate = self; - - _dataController = [[dataControllerClass alloc] initWithDataSource:self node:tableNode eventLog:eventLog]; - _dataController.delegate = _rangeController; - - _leadingScreensForBatching = 2.0; - _batchContext = [[ASBatchContext alloc] init]; - _visibleElements = [[NSCountedSet alloc] init]; - - _automaticallyAdjustsContentOffset = NO; - - _nodesConstrainedWidth = self.bounds.size.width; - - _proxyDelegate = [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self]; - super.delegate = (id)_proxyDelegate; - - _proxyDataSource = [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self]; - super.dataSource = (id)_proxyDataSource; - - [self registerClass:_ASTableViewCell.class forCellReuseIdentifier:kCellReuseIdentifier]; - - // iOS 11 automatically uses estimated heights, so disable those (see PR #485) - if (AS_AT_LEAST_IOS11) { - super.estimatedRowHeight = 0.0; - super.estimatedSectionHeaderHeight = 0.0; - super.estimatedSectionFooterHeight = 0.0; - } - - return self; -} - -- (instancetype)initWithCoder:(NSCoder *)aDecoder -{ - NSLog(@"Warning: AsyncDisplayKit is not designed to be used with Interface Builder. Table properties set in IB will be lost."); - return [self initWithFrame:CGRectZero style:UITableViewStylePlain]; -} - -- (void)dealloc -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeCAssert(_batchUpdateCount == 0, @"ASTableView deallocated in the middle of a batch update."); - - // Sometimes the UIKit classes can call back to their delegate even during deallocation. - _isDeallocating = YES; - if (!ASActivateExperimentalFeature(ASExperimentalCollectionTeardown)) { - [self setAsyncDelegate:nil]; - [self setAsyncDataSource:nil]; - } - - // Data controller & range controller may own a ton of nodes, let's deallocate those off-main - ASPerformBackgroundDeallocation(&_dataController); - ASPerformBackgroundDeallocation(&_rangeController); -} - -#pragma mark - -#pragma mark Overrides - -- (void)setDataSource:(id)dataSource -{ - // UIKit can internally generate a call to this method upon changing the asyncDataSource; only assert for non-nil. - ASDisplayNodeAssert(dataSource == nil, @"ASTableView uses asyncDataSource, not UITableView's dataSource property."); -} - -- (void)setDelegate:(id)delegate -{ - // Our UIScrollView superclass sets its delegate to nil on dealloc. Only assert if we get a non-nil value here. - ASDisplayNodeAssert(delegate == nil, @"ASTableView uses asyncDelegate, not UITableView's delegate property."); -} - -- (id)asyncDataSource -{ - return _asyncDataSource; -} - -- (void)setAsyncDataSource:(id)asyncDataSource -{ - // Changing super.dataSource will trigger a setNeedsLayout, so this must happen on the main thread. - ASDisplayNodeAssertMainThread(); - - // Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle - // the (common) case of nilling the asyncDataSource in the ViewController's dealloc. In this case our _asyncDataSource - // will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to hold a strong - // reference to the old dataSource in this case because calls to ASTableViewProxy will start failing and cause crashes. - NS_VALID_UNTIL_END_OF_SCOPE id oldDataSource = self.dataSource; - - if (asyncDataSource == nil) { - _asyncDataSource = nil; - _proxyDataSource = _isDeallocating ? nil : [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self]; - - memset(&_asyncDataSourceFlags, 0, sizeof(_asyncDataSourceFlags)); - } else { - _asyncDataSource = asyncDataSource; - _proxyDataSource = [[ASTableViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; - - _asyncDataSourceFlags.numberOfSectionsInTableView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]; - _asyncDataSourceFlags.numberOfSectionsInTableNode = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInTableNode:)]; - _asyncDataSourceFlags.tableViewNumberOfRowsInSection = [_asyncDataSource respondsToSelector:@selector(tableView:numberOfRowsInSection:)]; - _asyncDataSourceFlags.tableNodeNumberOfRowsInSection = [_asyncDataSource respondsToSelector:@selector(tableNode:numberOfRowsInSection:)]; - _asyncDataSourceFlags.tableViewNodeForRow = [_asyncDataSource respondsToSelector:@selector(tableView:nodeForRowAtIndexPath:)]; - _asyncDataSourceFlags.tableNodeNodeForRow = [_asyncDataSource respondsToSelector:@selector(tableNode:nodeForRowAtIndexPath:)]; - _asyncDataSourceFlags.tableViewNodeBlockForRow = [_asyncDataSource respondsToSelector:@selector(tableView:nodeBlockForRowAtIndexPath:)]; - _asyncDataSourceFlags.tableNodeNodeBlockForRow = [_asyncDataSource respondsToSelector:@selector(tableNode:nodeBlockForRowAtIndexPath:)]; - _asyncDataSourceFlags.tableViewCanMoveRow = [_asyncDataSource respondsToSelector:@selector(tableView:canMoveRowAtIndexPath:)]; - _asyncDataSourceFlags.tableViewMoveRow = [_asyncDataSource respondsToSelector:@selector(tableView:moveRowAtIndexPath:toIndexPath:)]; - _asyncDataSourceFlags.sectionIndexMethods = [_asyncDataSource respondsToSelector:@selector(sectionIndexTitlesForTableView:)] && [_asyncDataSource respondsToSelector:@selector(tableView:sectionForSectionIndexTitle:atIndex:)]; - _asyncDataSourceFlags.modelIdentifierMethods = [_asyncDataSource respondsToSelector:@selector(modelIdentifierForElementAtIndexPath:inNode:)] && [_asyncDataSource respondsToSelector:@selector(indexPathForElementWithModelIdentifier:inNode:)]; - - ASDisplayNodeAssert(_asyncDataSourceFlags.tableViewNodeBlockForRow - || _asyncDataSourceFlags.tableViewNodeForRow - || _asyncDataSourceFlags.tableNodeNodeBlockForRow - || _asyncDataSourceFlags.tableNodeNodeForRow, @"Data source must implement tableNode:nodeBlockForRowAtIndexPath: or tableNode:nodeForRowAtIndexPath:"); - ASDisplayNodeAssert(_asyncDataSourceFlags.tableNodeNumberOfRowsInSection || _asyncDataSourceFlags.tableViewNumberOfRowsInSection, @"Data source must implement tableNode:numberOfRowsInSection:"); - } - - _dataController.validationErrorSource = asyncDataSource; - super.dataSource = (id)_proxyDataSource; - [self _asyncDelegateOrDataSourceDidChange]; -} - -- (id)asyncDelegate -{ - return _asyncDelegate; -} - -- (void)setAsyncDelegate:(id)asyncDelegate -{ - // Changing super.delegate will trigger a setNeedsLayout, so this must happen on the main thread. - ASDisplayNodeAssertMainThread(); - - // Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle - // the (common) case of nilling the asyncDelegate in the ViewController's dealloc. In this case our _asyncDelegate - // will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to hold a strong - // reference to the old delegate in this case because calls to ASTableViewProxy will start failing and cause crashes. - NS_VALID_UNTIL_END_OF_SCOPE id oldDelegate = super.delegate; - - if (asyncDelegate == nil) { - _asyncDelegate = nil; - _proxyDelegate = _isDeallocating ? nil : [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self]; - - memset(&_asyncDelegateFlags, 0, sizeof(_asyncDelegateFlags)); - } else { - _asyncDelegate = asyncDelegate; - _proxyDelegate = [[ASTableViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self]; - - _asyncDelegateFlags.scrollViewDidScroll = [_asyncDelegate respondsToSelector:@selector(scrollViewDidScroll:)]; - - _asyncDelegateFlags.tableViewWillDisplayNodeForRow = [_asyncDelegate respondsToSelector:@selector(tableView:willDisplayNode:forRowAtIndexPath:)]; - _asyncDelegateFlags.tableNodeWillDisplayNodeForRow = [_asyncDelegate respondsToSelector:@selector(tableNode:willDisplayRowWithNode:)]; - if (_asyncDelegateFlags.tableViewWillDisplayNodeForRow == NO) { - _asyncDelegateFlags.tableViewWillDisplayNodeForRowDeprecated = [_asyncDelegate respondsToSelector:@selector(tableView:willDisplayNodeForRowAtIndexPath:)]; - } - _asyncDelegateFlags.tableViewDidEndDisplayingNodeForRow = [_asyncDelegate respondsToSelector:@selector(tableView:didEndDisplayingNode:forRowAtIndexPath:)]; - _asyncDelegateFlags.tableNodeDidEndDisplayingNodeForRow = [_asyncDelegate respondsToSelector:@selector(tableNode:didEndDisplayingRowWithNode:)]; - _asyncDelegateFlags.scrollViewWillEndDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]; - _asyncDelegateFlags.scrollViewDidEndDecelerating = [_asyncDelegate respondsToSelector:@selector(scrollViewDidEndDecelerating:)]; - _asyncDelegateFlags.tableViewWillBeginBatchFetch = [_asyncDelegate respondsToSelector:@selector(tableView:willBeginBatchFetchWithContext:)]; - _asyncDelegateFlags.tableNodeWillBeginBatchFetch = [_asyncDelegate respondsToSelector:@selector(tableNode:willBeginBatchFetchWithContext:)]; - _asyncDelegateFlags.shouldBatchFetchForTableView = [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForTableView:)]; - _asyncDelegateFlags.shouldBatchFetchForTableNode = [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForTableNode:)]; - _asyncDelegateFlags.scrollViewWillBeginDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewWillBeginDragging:)]; - _asyncDelegateFlags.scrollViewDidEndDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewDidEndDragging:willDecelerate:)]; - _asyncDelegateFlags.tableViewConstrainedSizeForRow = [_asyncDelegate respondsToSelector:@selector(tableView:constrainedSizeForRowAtIndexPath:)]; - _asyncDelegateFlags.tableNodeConstrainedSizeForRow = [_asyncDelegate respondsToSelector:@selector(tableNode:constrainedSizeForRowAtIndexPath:)]; - - _asyncDelegateFlags.tableViewWillSelectRow = [_asyncDelegate respondsToSelector:@selector(tableView:willSelectRowAtIndexPath:)]; - _asyncDelegateFlags.tableNodeWillSelectRow = [_asyncDelegate respondsToSelector:@selector(tableNode:willSelectRowAtIndexPath:)]; - _asyncDelegateFlags.tableViewDidSelectRow = [_asyncDelegate respondsToSelector:@selector(tableView:didSelectRowAtIndexPath:)]; - _asyncDelegateFlags.tableNodeDidSelectRow = [_asyncDelegate respondsToSelector:@selector(tableNode:didSelectRowAtIndexPath:)]; - _asyncDelegateFlags.tableViewWillDeselectRow = [_asyncDelegate respondsToSelector:@selector(tableView:willDeselectRowAtIndexPath:)]; - _asyncDelegateFlags.tableNodeWillDeselectRow = [_asyncDelegate respondsToSelector:@selector(tableNode:willDeselectRowAtIndexPath:)]; - _asyncDelegateFlags.tableViewDidDeselectRow = [_asyncDelegate respondsToSelector:@selector(tableView:didDeselectRowAtIndexPath:)]; - _asyncDelegateFlags.tableNodeDidDeselectRow = [_asyncDelegate respondsToSelector:@selector(tableNode:didDeselectRowAtIndexPath:)]; - _asyncDelegateFlags.tableViewShouldHighlightRow = [_asyncDelegate respondsToSelector:@selector(tableView:shouldHighlightRowAtIndexPath:)]; - _asyncDelegateFlags.tableNodeShouldHighlightRow = [_asyncDelegate respondsToSelector:@selector(tableNode:shouldHighlightRowAtIndexPath:)]; - _asyncDelegateFlags.tableViewDidHighlightRow = [_asyncDelegate respondsToSelector:@selector(tableView:didHighlightRowAtIndexPath:)]; - _asyncDelegateFlags.tableNodeDidHighlightRow = [_asyncDelegate respondsToSelector:@selector(tableNode:didHighlightRowAtIndexPath:)]; - _asyncDelegateFlags.tableViewDidUnhighlightRow = [_asyncDelegate respondsToSelector:@selector(tableView:didUnhighlightRowAtIndexPath:)]; - _asyncDelegateFlags.tableNodeDidUnhighlightRow = [_asyncDelegate respondsToSelector:@selector(tableNode:didUnhighlightRowAtIndexPath:)]; - _asyncDelegateFlags.tableViewShouldShowMenuForRow = [_asyncDelegate respondsToSelector:@selector(tableView:shouldShowMenuForRowAtIndexPath:)]; - _asyncDelegateFlags.tableNodeShouldShowMenuForRow = [_asyncDelegate respondsToSelector:@selector(tableNode:shouldShowMenuForRowAtIndexPath:)]; - _asyncDelegateFlags.tableViewCanPerformActionForRow = [_asyncDelegate respondsToSelector:@selector(tableView:canPerformAction:forRowAtIndexPath:withSender:)]; - _asyncDelegateFlags.tableNodeCanPerformActionForRow = [_asyncDelegate respondsToSelector:@selector(tableNode:canPerformAction:forRowAtIndexPath:withSender:)]; - _asyncDelegateFlags.tableViewPerformActionForRow = [_asyncDelegate respondsToSelector:@selector(tableView:performAction:forRowAtIndexPath:withSender:)]; - _asyncDelegateFlags.tableNodePerformActionForRow = [_asyncDelegate respondsToSelector:@selector(tableNode:performAction:forRowAtIndexPath:withSender:)]; - } - - super.delegate = (id)_proxyDelegate; - [self _asyncDelegateOrDataSourceDidChange]; -} - -- (void)_asyncDelegateOrDataSourceDidChange -{ - ASDisplayNodeAssertMainThread(); - - if (_asyncDataSource == nil && _asyncDelegate == nil && !ASActivateExperimentalFeature(ASExperimentalSkipClearData)) { - [_dataController clearData]; - } -} - -- (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy -{ - if (proxy == _proxyDelegate) { - [self setAsyncDelegate:nil]; - } else if (proxy == _proxyDataSource) { - [self setAsyncDataSource:nil]; - } -} - -- (void)reloadDataWithCompletion:(void (^)())completion -{ - ASDisplayNodeAssertMainThread(); - - if (! _dataController.initialReloadDataHasBeenCalled) { - // If this is the first reload, forward to super immediately to prevent it from triggering more "initial" loads while our data controller is working. - [super reloadData]; - } - - void (^batchUpdatesCompletion)(BOOL); - if (completion) { - batchUpdatesCompletion = ^(BOOL) { - completion(); - }; - } - - [self beginUpdates]; - [_changeSet reloadData]; - [self endUpdatesWithCompletion:batchUpdatesCompletion]; -} - -- (void)reloadData -{ - [self reloadDataWithCompletion:nil]; -} - -- (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated -{ - if ([self validateIndexPath:indexPath]) { - [super scrollToRowAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated]; - } -} - -- (void)relayoutItems -{ - [_dataController relayoutAllNodesWithInvalidationBlock:nil]; -} - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType -{ - [_rangeController setTuningParameters:tuningParameters forRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; -} - -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType -{ - return [_rangeController tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; -} - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - [_rangeController setTuningParameters:tuningParameters forRangeMode:rangeMode rangeType:rangeType]; -} - -- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - return [_rangeController tuningParametersForRangeMode:rangeMode rangeType:rangeType]; -} - -- (ASElementMap *)elementMapForRangeController:(ASRangeController *)rangeController -{ - return _dataController.visibleMap; -} - -- (ASCellNode *)nodeForRowAtIndexPath:(NSIndexPath *)indexPath -{ - return [_dataController.visibleMap elementForItemAtIndexPath:indexPath].node; -} - -- (NSIndexPath *)convertIndexPathFromTableNode:(NSIndexPath *)indexPath waitingIfNeeded:(BOOL)wait -{ - NSIndexPath *viewIndexPath = [_dataController.visibleMap convertIndexPath:indexPath fromMap:_dataController.pendingMap]; - if (viewIndexPath == nil && wait) { - [self waitUntilAllUpdatesAreCommitted]; - return [self convertIndexPathFromTableNode:indexPath waitingIfNeeded:NO]; - } - return viewIndexPath; -} - -- (NSIndexPath *)convertIndexPathToTableNode:(NSIndexPath *)indexPath -{ - if ([self validateIndexPath:indexPath] == nil) { - return nil; - } - - return [_dataController.pendingMap convertIndexPath:indexPath fromMap:_dataController.visibleMap]; -} - -- (NSArray *)convertIndexPathsToTableNode:(NSArray *)indexPaths -{ - if (indexPaths == nil) { - return nil; - } - - NSMutableArray *indexPathsArray = [NSMutableArray new]; - - for (NSIndexPath *indexPathInView in indexPaths) { - NSIndexPath *indexPath = [self convertIndexPathToTableNode:indexPathInView]; - if (indexPath != nil) { - [indexPathsArray addObject:indexPath]; - } - } - return indexPathsArray; -} - -- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode -{ - return [self indexPathForNode:cellNode waitingIfNeeded:NO]; -} - -/** - * Asserts that the index path is a valid view-index-path, and returns it if so, nil otherwise. - */ -- (nullable NSIndexPath *)validateIndexPath:(nullable NSIndexPath *)indexPath -{ - if (indexPath == nil) { - return nil; - } - - NSInteger section = indexPath.section; - if (section >= self.numberOfSections) { - ASDisplayNodeFailAssert(@"Table view index path has invalid section %lu, section count = %lu", (unsigned long)section, (unsigned long)self.numberOfSections); - return nil; - } - - NSInteger item = indexPath.item; - // item == NSNotFound means e.g. "scroll to this section" and is acceptable - if (item != NSNotFound && item >= [self numberOfRowsInSection:section]) { - ASDisplayNodeFailAssert(@"Table view index path has invalid item %lu in section %lu, item count = %lu", (unsigned long)indexPath.item, (unsigned long)section, (unsigned long)[self numberOfRowsInSection:section]); - return nil; - } - - return indexPath; -} - -- (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode waitingIfNeeded:(BOOL)wait -{ - if (cellNode == nil) { - return nil; - } - - NSIndexPath *indexPath = [_dataController.visibleMap indexPathForElement:cellNode.collectionElement]; - indexPath = [self validateIndexPath:indexPath]; - if (indexPath == nil && wait) { - [self waitUntilAllUpdatesAreCommitted]; - return [self indexPathForNode:cellNode waitingIfNeeded:NO]; - } - return indexPath; -} - -- (NSArray *)visibleNodes -{ - const auto elements = [self visibleElementsForRangeController:_rangeController]; - return ASArrayByFlatMapping(elements, ASCollectionElement *e, e.node); -} - -- (void)beginUpdates -{ - ASDisplayNodeAssertMainThread(); - // _changeSet must be available during batch update - ASDisplayNodeAssertTrue((_batchUpdateCount > 0) == (_changeSet != nil)); - - if (_batchUpdateCount == 0) { - _changeSet = [[_ASHierarchyChangeSet alloc] initWithOldData:[_dataController itemCountsFromDataSource]]; - } - _batchUpdateCount++; -} - -- (void)endUpdates -{ - [self endUpdatesWithCompletion:nil]; -} - -- (void)endUpdatesWithCompletion:(void (^)(BOOL completed))completion -{ - // We capture the current state of whether animations are enabled if they don't provide us with one. - [self endUpdatesAnimated:[UIView areAnimationsEnabled] completion:completion]; -} - -- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL completed))completion -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssertNotNil(_changeSet, @"_changeSet must be available when batch update ends"); - - _batchUpdateCount--; - // Prevent calling endUpdatesAnimated:completion: in an unbalanced way - NSAssert(_batchUpdateCount >= 0, @"endUpdatesAnimated:completion: called without having a balanced beginUpdates call"); - - [_changeSet addCompletionHandler:completion]; - - if (_batchUpdateCount == 0) { - _ASHierarchyChangeSet *changeSet = _changeSet; - // Nil out _changeSet before forwarding to _dataController to allow the change set to cause subsequent batch updates on the same run loop - _changeSet = nil; - changeSet.animated = animated; - [_dataController updateWithChangeSet:changeSet]; - } -} - -- (BOOL)isProcessingUpdates -{ - return [_dataController isProcessingUpdates]; -} - -- (void)onDidFinishProcessingUpdates:(void (^)())completion -{ - [_dataController onDidFinishProcessingUpdates:completion]; -} - -- (void)waitUntilAllUpdatesAreCommitted -{ - ASDisplayNodeAssertMainThread(); - if (_batchUpdateCount > 0) { - // This assertion will be enabled soon. - // ASDisplayNodeFailAssert(@"Should not call %@ during batch update", NSStringFromSelector(_cmd)); - return; - } - - [_dataController waitUntilAllUpdatesAreProcessed]; -} - -- (void)layoutSubviews -{ - // Remeasure all rows if our row width has changed. - UIEdgeInsets contentInset = self.contentInset; - CGFloat constrainedWidth = self.bounds.size.width - [self sectionIndexWidth] - contentInset.left - contentInset.right; - if (constrainedWidth > 0 && _nodesConstrainedWidth != constrainedWidth) { - _nodesConstrainedWidth = constrainedWidth; - [_cellsForLayoutUpdates removeAllObjects]; - - [self beginUpdates]; - [_dataController relayoutAllNodesWithInvalidationBlock:nil]; - [self endUpdatesAnimated:(ASDisplayNodeLayerHasAnimations(self.layer) == NO) completion:nil]; - } else { - if (_cellsForLayoutUpdates.count > 0) { - NSArray *nodes = [_cellsForLayoutUpdates allObjects]; - [_cellsForLayoutUpdates removeAllObjects]; - - const auto nodesSizeChanged = [[NSMutableArray alloc] init]; - [_dataController relayoutNodes:nodes nodesSizeChanged:nodesSizeChanged]; - if (nodesSizeChanged.count > 0) { - [self requeryNodeHeights]; - } - } - } - - // To ensure _nodesConstrainedWidth is up-to-date for every usage, this call to super must be done last - [super layoutSubviews]; - [_rangeController updateIfNeeded]; -} - -#pragma mark - -#pragma mark Editing - -- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation -{ - ASDisplayNodeAssertMainThread(); - if (sections.count == 0) { return; } - [self beginUpdates]; - [_changeSet insertSections:sections animationOptions:animation]; - [self endUpdates]; -} - -- (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation -{ - ASDisplayNodeAssertMainThread(); - if (sections.count == 0) { return; } - [self beginUpdates]; - [_changeSet deleteSections:sections animationOptions:animation]; - [self endUpdates]; -} - -- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation -{ - ASDisplayNodeAssertMainThread(); - if (sections.count == 0) { return; } - [self beginUpdates]; - [_changeSet reloadSections:sections animationOptions:animation]; - [self endUpdates]; -} - -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet moveSection:section toSection:newSection animationOptions:UITableViewRowAnimationNone]; - [self endUpdates]; -} - -- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation -{ - ASDisplayNodeAssertMainThread(); - if (indexPaths.count == 0) { return; } - [self beginUpdates]; - [_changeSet insertItems:indexPaths animationOptions:animation]; - [self endUpdates]; -} - -- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation -{ - ASDisplayNodeAssertMainThread(); - if (indexPaths.count == 0) { return; } - [self beginUpdates]; - [_changeSet deleteItems:indexPaths animationOptions:animation]; - [self endUpdates]; -} - -- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation -{ - ASDisplayNodeAssertMainThread(); - if (indexPaths.count == 0) { return; } - [self beginUpdates]; - [_changeSet reloadItems:indexPaths animationOptions:animation]; - [self endUpdates]; -} - -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet moveItemAtIndexPath:indexPath toIndexPath:newIndexPath animationOptions:UITableViewRowAnimationNone]; - [self endUpdates]; -} - -#pragma mark - -#pragma mark adjust content offset - -- (void)beginAdjustingContentOffset -{ - NSIndexPath *firstVisibleIndexPath = [self.indexPathsForVisibleRows sortedArrayUsingSelector:@selector(compare:)].firstObject; - if (firstVisibleIndexPath) { - ASCellNode *node = [self nodeForRowAtIndexPath:firstVisibleIndexPath]; - if (node) { - _contentOffsetAdjustmentTopVisibleNode = node; - _contentOffsetAdjustmentTopVisibleNodeOffset = [self rectForRowAtIndexPath:firstVisibleIndexPath].origin.y - self.bounds.origin.y; - } - } -} - -- (void)endAdjustingContentOffsetAnimated:(BOOL)animated -{ - // We can't do this for animated updates. - if (animated) { - return; - } - - // We can't do this if we didn't have a top visible row before. - if (_contentOffsetAdjustmentTopVisibleNode == nil) { - return; - } - - NSIndexPath *newIndexPathForTopVisibleRow = [self indexPathForNode:_contentOffsetAdjustmentTopVisibleNode]; - // We can't do this if our top visible row was deleted - if (newIndexPathForTopVisibleRow == nil) { - return; - } - - CGFloat newRowOriginYInSelf = [self rectForRowAtIndexPath:newIndexPathForTopVisibleRow].origin.y - self.bounds.origin.y; - CGPoint newContentOffset = self.contentOffset; - newContentOffset.y += (newRowOriginYInSelf - _contentOffsetAdjustmentTopVisibleNodeOffset); - self.contentOffset = newContentOffset; - _contentOffsetAdjustmentTopVisibleNode = nil; -} - -#pragma mark - Intercepted selectors - -- (void)setTableHeaderView:(UIView *)tableHeaderView -{ - // Typically the view will be nil before setting it, but reset state if it is being re-hosted. - [self.tableHeaderView.asyncdisplaykit_node exitHierarchyState:ASHierarchyStateRangeManaged]; - [super setTableHeaderView:tableHeaderView]; - [self.tableHeaderView.asyncdisplaykit_node enterHierarchyState:ASHierarchyStateRangeManaged]; -} - -- (void)setTableFooterView:(UIView *)tableFooterView -{ - // Typically the view will be nil before setting it, but reset state if it is being re-hosted. - [self.tableFooterView.asyncdisplaykit_node exitHierarchyState:ASHierarchyStateRangeManaged]; - [super setTableFooterView:tableFooterView]; - [self.tableFooterView.asyncdisplaykit_node enterHierarchyState:ASHierarchyStateRangeManaged]; -} - -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath -{ - _ASTableViewCell *cell = [self dequeueReusableCellWithIdentifier:kCellReuseIdentifier forIndexPath:indexPath]; - cell.delegate = self; - - ASCollectionElement *element = [_dataController.visibleMap elementForItemAtIndexPath:indexPath]; - cell.element = element; - ASCellNode *node = element.node; - if (node) { - [_rangeController configureContentView:cell.contentView forCellNode:node]; - } - - return cell; -} - -- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath -{ - CGFloat height = 0.0; - - ASCollectionElement *element = [_dataController.visibleMap elementForItemAtIndexPath:indexPath]; - if (element != nil) { - ASCellNode *node = element.node; - ASDisplayNodeAssertNotNil(node, @"Node must not be nil!"); - height = [node layoutThatFits:element.constrainedSize].size.height; - } - -#if TARGET_OS_IOS - /** - * Weirdly enough, Apple expects the return value here to _include_ the height - * of the separator, if there is one! So if our node wants to be 43.5, we need - * to return 44. UITableView will make a cell of height 44 with a content view - * of height 43.5. - */ - if (tableView.separatorStyle != UITableViewCellSeparatorStyleNone) { - height += 1.0 / ASScreenScale(); - } -#endif - - return height; -} - -- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView -{ - return _dataController.visibleMap.numberOfSections; -} - -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section -{ - return [_dataController.visibleMap numberOfItemsInSection:section]; -} - -- (nullable NSString *)modelIdentifierForElementAtIndexPath:(NSIndexPath *)indexPath inView:(UIView *)view { - if (_asyncDataSourceFlags.modelIdentifierMethods) { - GET_TABLENODE_OR_RETURN(tableNode, nil); - NSIndexPath *convertedPath = [self convertIndexPathToTableNode:indexPath]; - if (convertedPath == nil) { - return nil; - } else { - return [_asyncDataSource modelIdentifierForElementAtIndexPath:convertedPath inNode:tableNode]; - } - } else { - return nil; - } -} - -- (nullable NSIndexPath *)indexPathForElementWithModelIdentifier:(NSString *)identifier inView:(UIView *)view { - if (_asyncDataSourceFlags.modelIdentifierMethods) { - GET_TABLENODE_OR_RETURN(tableNode, nil); - return [_asyncDataSource indexPathForElementWithModelIdentifier:identifier inNode:tableNode]; - } else { - return nil; - } -} - -- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath -{ - if (_asyncDataSourceFlags.tableViewCanMoveRow) { - return [_asyncDataSource tableView:self canMoveRowAtIndexPath:indexPath]; - } else { - return NO; - } -} - -- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath -{ - if (_asyncDataSourceFlags.tableViewMoveRow) { - [_asyncDataSource tableView:self moveRowAtIndexPath:sourceIndexPath toIndexPath:destinationIndexPath]; - } - - // Move node after informing data source in case they call nodeAtIndexPath: - // Get up to date - [self waitUntilAllUpdatesAreCommitted]; - // Set our flag to suppress informing super about the change. - _updatingInResponseToInteractiveMove = YES; - // Submit the move - [self moveRowAtIndexPath:sourceIndexPath toIndexPath:destinationIndexPath]; - // Wait for it to finish – should be fast! - [self waitUntilAllUpdatesAreCommitted]; - // Clear the flag - _updatingInResponseToInteractiveMove = NO; -} - -- (void)tableView:(UITableView *)tableView willDisplayCell:(_ASTableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath -{ - ASCollectionElement *element = cell.element; - if (element) { - ASDisplayNodeAssertTrue([_dataController.visibleMap elementForItemAtIndexPath:indexPath] == element); - [_visibleElements addObject:element]; - } else { - ASDisplayNodeAssert(NO, @"Unexpected nil element for willDisplayCell: %@, %@, %@", cell, self, indexPath); - return; - } - - ASCellNode *cellNode = element.node; - cellNode.scrollView = tableView; - - ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with cell that will be displayed not to be nil. indexPath: %@", indexPath); - - if (_asyncDelegateFlags.tableNodeWillDisplayNodeForRow) { - GET_TABLENODE_OR_RETURN(tableNode, (void)0); - [_asyncDelegate tableNode:tableNode willDisplayRowWithNode:cellNode]; - } else if (_asyncDelegateFlags.tableViewWillDisplayNodeForRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate tableView:self willDisplayNode:cellNode forRowAtIndexPath:indexPath]; - } else if (_asyncDelegateFlags.tableViewWillDisplayNodeForRowDeprecated) { - [_asyncDelegate tableView:self willDisplayNodeForRowAtIndexPath:indexPath]; - } -#pragma clang diagnostic pop - - [_rangeController setNeedsUpdate]; - - if ([cell consumesCellNodeVisibilityEvents]) { - [_cellsForVisibilityUpdates addObject:cell]; - } -} - -- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(_ASTableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath -{ - // Retrieve the element from cell instead of visible map because at this point visible map could have been updated and no longer holds the element. - ASCollectionElement *element = cell.element; - if (element) { - [_visibleElements removeObject:element]; - } else { - ASDisplayNodeAssert(NO, @"Unexpected nil element for didEndDisplayingCell: %@, %@, %@", cell, self, indexPath); - return; - } - - ASCellNode *cellNode = element.node; - - [_rangeController setNeedsUpdate]; - - ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with removed cell not to be nil."); - if (_asyncDelegateFlags.tableNodeDidEndDisplayingNodeForRow) { - if (ASTableNode *tableNode = self.tableNode) { - [_asyncDelegate tableNode:tableNode didEndDisplayingRowWithNode:cellNode]; - } - } else if (_asyncDelegateFlags.tableViewDidEndDisplayingNodeForRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate tableView:self didEndDisplayingNode:cellNode forRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } - - [_cellsForVisibilityUpdates removeObject:cell]; - - cellNode.scrollView = nil; -} - -- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(nonnull NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.tableNodeWillSelectRow) { - GET_TABLENODE_OR_RETURN(tableNode, indexPath); - NSIndexPath *result = [self convertIndexPathToTableNode:indexPath]; - // If this item was is gone, just let the table view do its default behavior and select. - if (result == nil) { - return indexPath; - } else { - result = [_asyncDelegate tableNode:tableNode willSelectRowAtIndexPath:result]; - result = [self convertIndexPathFromTableNode:result waitingIfNeeded:YES]; - return result; - } - } else if (_asyncDelegateFlags.tableViewWillSelectRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate tableView:self willSelectRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } else { - return indexPath; - } -} - -- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(nonnull NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.tableNodeDidSelectRow) { - GET_TABLENODE_OR_RETURN(tableNode, (void)0); - indexPath = [self convertIndexPathToTableNode:indexPath]; - if (indexPath != nil) { - [_asyncDelegate tableNode:tableNode didSelectRowAtIndexPath:indexPath]; - } - } else if (_asyncDelegateFlags.tableViewDidSelectRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate tableView:self didSelectRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } -} - -- (NSIndexPath *)tableView:(UITableView *)tableView willDeselectRowAtIndexPath:(nonnull NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.tableNodeWillDeselectRow) { - GET_TABLENODE_OR_RETURN(tableNode, indexPath); - NSIndexPath *result = [self convertIndexPathToTableNode:indexPath]; - // If this item was is gone, just let the table view do its default behavior and deselect. - if (result == nil) { - return indexPath; - } else { - result = [_asyncDelegate tableNode:tableNode willDeselectRowAtIndexPath:result]; - result = [self convertIndexPathFromTableNode:result waitingIfNeeded:YES]; - return result; - } - } else if (_asyncDelegateFlags.tableViewWillDeselectRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate tableView:self willDeselectRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } - return indexPath; -} - -- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(nonnull NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.tableNodeDidDeselectRow) { - GET_TABLENODE_OR_RETURN(tableNode, (void)0); - indexPath = [self convertIndexPathToTableNode:indexPath]; - if (indexPath != nil) { - [_asyncDelegate tableNode:tableNode didDeselectRowAtIndexPath:indexPath]; - } - } else if (_asyncDelegateFlags.tableViewDidDeselectRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate tableView:self didDeselectRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } -} - -- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(nonnull NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.tableNodeShouldHighlightRow) { - GET_TABLENODE_OR_RETURN(tableNode, NO); - indexPath = [self convertIndexPathToTableNode:indexPath]; - if (indexPath != nil) { - return [_asyncDelegate tableNode:tableNode shouldHighlightRowAtIndexPath:indexPath]; - } - } else if (_asyncDelegateFlags.tableViewShouldHighlightRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate tableView:self shouldHighlightRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } - return YES; -} - -- (void)tableView:(UITableView *)tableView didHighlightRowAtIndexPath:(nonnull NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.tableNodeDidHighlightRow) { - GET_TABLENODE_OR_RETURN(tableNode, (void)0); - indexPath = [self convertIndexPathToTableNode:indexPath]; - if (indexPath != nil) { - return [_asyncDelegate tableNode:tableNode didHighlightRowAtIndexPath:indexPath]; - } - } else if (_asyncDelegateFlags.tableViewDidHighlightRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate tableView:self didHighlightRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } -} - -- (void)tableView:(UITableView *)tableView didUnhighlightRowAtIndexPath:(nonnull NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.tableNodeDidHighlightRow) { - GET_TABLENODE_OR_RETURN(tableNode, (void)0); - indexPath = [self convertIndexPathToTableNode:indexPath]; - if (indexPath != nil) { - return [_asyncDelegate tableNode:tableNode didUnhighlightRowAtIndexPath:indexPath]; - } - } else if (_asyncDelegateFlags.tableViewDidUnhighlightRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate tableView:self didUnhighlightRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } -} - -- (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(nonnull NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.tableNodeShouldShowMenuForRow) { - GET_TABLENODE_OR_RETURN(tableNode, NO); - indexPath = [self convertIndexPathToTableNode:indexPath]; - if (indexPath != nil) { - return [_asyncDelegate tableNode:tableNode shouldShowMenuForRowAtIndexPath:indexPath]; - } - } else if (_asyncDelegateFlags.tableViewShouldShowMenuForRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate tableView:self shouldShowMenuForRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } - return NO; -} - -- (BOOL)tableView:(UITableView *)tableView canPerformAction:(nonnull SEL)action forRowAtIndexPath:(nonnull NSIndexPath *)indexPath withSender:(nullable id)sender -{ - if (_asyncDelegateFlags.tableNodeCanPerformActionForRow) { - GET_TABLENODE_OR_RETURN(tableNode, NO); - indexPath = [self convertIndexPathToTableNode:indexPath]; - if (indexPath != nil) { - return [_asyncDelegate tableNode:tableNode canPerformAction:action forRowAtIndexPath:indexPath withSender:sender]; - } - } else if (_asyncDelegateFlags.tableViewCanPerformActionForRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate tableView:self canPerformAction:action forRowAtIndexPath:indexPath withSender:sender]; -#pragma clang diagnostic pop - } - return NO; -} - -- (void)tableView:(UITableView *)tableView performAction:(nonnull SEL)action forRowAtIndexPath:(nonnull NSIndexPath *)indexPath withSender:(nullable id)sender -{ - if (_asyncDelegateFlags.tableNodePerformActionForRow) { - GET_TABLENODE_OR_RETURN(tableNode, (void)0); - indexPath = [self convertIndexPathToTableNode:indexPath]; - if (indexPath != nil) { - [_asyncDelegate tableNode:tableNode performAction:action forRowAtIndexPath:indexPath withSender:sender]; - } - } else if (_asyncDelegateFlags.tableViewPerformActionForRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate tableView:self performAction:action forRowAtIndexPath:indexPath withSender:sender]; -#pragma clang diagnostic pop - } -} - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView -{ - if (scrollView != self && UITABLEVIEW_RESPONDS_TO_SELECTOR()) { - [super scrollViewDidScroll:scrollView]; - return; - } - ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController]; - if (ASInterfaceStateIncludesVisible(interfaceState)) { - [self _checkForBatchFetching]; - } - for (_ASTableViewCell *tableCell in _cellsForVisibilityUpdates) { - [[tableCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventVisibleRectChanged - inScrollView:scrollView - withCellFrame:tableCell.frame]; - } - if (_asyncDelegateFlags.scrollViewDidScroll) { - [_asyncDelegate scrollViewDidScroll:scrollView]; - } -} - -- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset -{ - if (scrollView != self && UITABLEVIEW_RESPONDS_TO_SELECTOR()) { - [super scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset]; - return; - } - CGPoint contentOffset = scrollView.contentOffset; - _deceleratingVelocity = CGPointMake( - contentOffset.x - ((targetContentOffset != NULL) ? targetContentOffset->x : 0), - contentOffset.y - ((targetContentOffset != NULL) ? targetContentOffset->y : 0) - ); - - if (targetContentOffset != NULL) { - ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist"); - [self _beginBatchFetchingIfNeededWithContentOffset:*targetContentOffset velocity:velocity]; - } - - if (_asyncDelegateFlags.scrollViewWillEndDragging) { - [_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:(targetContentOffset ? : &contentOffset)]; - } -} - -- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView -{ - if (scrollView != self && UITABLEVIEW_RESPONDS_TO_SELECTOR()) { - [super scrollViewDidEndDecelerating:scrollView]; - return; - } - _deceleratingVelocity = CGPointZero; - - if (_asyncDelegateFlags.scrollViewDidEndDecelerating) { - [_asyncDelegate scrollViewDidEndDecelerating:scrollView]; - } -} - -- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView -{ - if (scrollView != self && UITABLEVIEW_RESPONDS_TO_SELECTOR()) { - [super scrollViewWillBeginDragging:scrollView]; - return; - } - // If a scroll happens the current range mode needs to go to full - _rangeController.contentHasBeenScrolled = YES; - [_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull]; - - for (_ASTableViewCell *tableViewCell in _cellsForVisibilityUpdates) { - [[tableViewCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventWillBeginDragging - inScrollView:scrollView - withCellFrame:tableViewCell.frame]; - } - if (_asyncDelegateFlags.scrollViewWillBeginDragging) { - [_asyncDelegate scrollViewWillBeginDragging:scrollView]; - } -} - -- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate -{ - if (scrollView != self && UITABLEVIEW_RESPONDS_TO_SELECTOR()) { - [super scrollViewDidEndDragging:scrollView willDecelerate:decelerate]; - return; - } - for (_ASTableViewCell *tableViewCell in _cellsForVisibilityUpdates) { - [[tableViewCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventDidEndDragging - inScrollView:scrollView - withCellFrame:tableViewCell.frame]; - } - if (_asyncDelegateFlags.scrollViewDidEndDragging) { - [_asyncDelegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate]; - } -} - -#pragma mark - Misc - -- (BOOL)inverted -{ - return _inverted; -} - -- (void)setInverted:(BOOL)inverted -{ - _inverted = inverted; -} - -- (CGFloat)leadingScreensForBatching -{ - return _leadingScreensForBatching; -} - -- (void)setLeadingScreensForBatching:(CGFloat)leadingScreensForBatching -{ - if (_leadingScreensForBatching != leadingScreensForBatching) { - _leadingScreensForBatching = leadingScreensForBatching; - ASPerformBlockOnMainThread(^{ - [self _checkForBatchFetching]; - }); - } -} - -- (BOOL)automaticallyAdjustsContentOffset -{ - return _automaticallyAdjustsContentOffset; -} - -- (void)setAutomaticallyAdjustsContentOffset:(BOOL)automaticallyAdjustsContentOffset -{ - _automaticallyAdjustsContentOffset = automaticallyAdjustsContentOffset; -} - -#pragma mark - Scroll Direction - -- (ASScrollDirection)scrollDirection -{ - CGPoint scrollVelocity; - if (self.isTracking) { - scrollVelocity = [self.panGestureRecognizer velocityInView:self.superview]; - } else { - scrollVelocity = _deceleratingVelocity; - } - - ASScrollDirection scrollDirection = [self _scrollDirectionForVelocity:scrollVelocity]; - return ASScrollDirectionApplyTransform(scrollDirection, self.transform); -} - -- (ASScrollDirection)_scrollDirectionForVelocity:(CGPoint)scrollVelocity -{ - ASScrollDirection direction = ASScrollDirectionNone; - ASScrollDirection scrollableDirections = [self scrollableDirections]; - - if (ASScrollDirectionContainsVerticalDirection(scrollableDirections)) { // Can scroll vertically. - if (scrollVelocity.y < 0.0) { - direction |= ASScrollDirectionDown; - } else if (scrollVelocity.y > 0.0) { - direction |= ASScrollDirectionUp; - } - } - - return direction; -} - -- (ASScrollDirection)scrollableDirections -{ - ASScrollDirection scrollableDirection = ASScrollDirectionNone; - CGFloat totalContentWidth = self.contentSize.width + self.contentInset.left + self.contentInset.right; - CGFloat totalContentHeight = self.contentSize.height + self.contentInset.top + self.contentInset.bottom; - - if (self.alwaysBounceHorizontal || totalContentWidth > self.bounds.size.width) { // Can scroll horizontally. - scrollableDirection |= ASScrollDirectionHorizontalDirections; - } - if (self.alwaysBounceVertical || totalContentHeight > self.bounds.size.height) { // Can scroll vertically. - scrollableDirection |= ASScrollDirectionVerticalDirections; - } - return scrollableDirection; -} - - -#pragma mark - Batch Fetching - -- (ASBatchContext *)batchContext -{ - return _batchContext; -} - -- (BOOL)canBatchFetch -{ - // if the delegate does not respond to this method, there is no point in starting to fetch - BOOL canFetch = _asyncDelegateFlags.tableNodeWillBeginBatchFetch || _asyncDelegateFlags.tableViewWillBeginBatchFetch; - if (canFetch && _asyncDelegateFlags.shouldBatchFetchForTableNode) { - GET_TABLENODE_OR_RETURN(tableNode, NO); - return [_asyncDelegate shouldBatchFetchForTableNode:tableNode]; - } else if (canFetch && _asyncDelegateFlags.shouldBatchFetchForTableView) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate shouldBatchFetchForTableView:self]; -#pragma clang diagnostic pop - } else { - return canFetch; - } -} - -- (id)batchFetchingDelegate -{ - return self.tableNode.batchFetchingDelegate; -} - -- (void)_scheduleCheckForBatchFetchingForNumberOfChanges:(NSUInteger)changes -{ - // Prevent fetching will continually trigger in a loop after reaching end of content and no new content was provided - if (changes == 0 && _hasEverCheckedForBatchFetchingDueToUpdate) { - return; - } - _hasEverCheckedForBatchFetchingDueToUpdate = YES; - - // Push this to the next runloop to be sure the scroll view has the right content size - dispatch_async(dispatch_get_main_queue(), ^{ - [self _checkForBatchFetching]; - }); -} - -- (void)_checkForBatchFetching -{ - // Dragging will be handled in scrollViewWillEndDragging:withVelocity:targetContentOffset: - if (self.isDragging || self.isTracking) { - return; - } - - [self _beginBatchFetchingIfNeededWithContentOffset:self.contentOffset velocity:CGPointZero]; -} - -- (void)_beginBatchFetchingIfNeededWithContentOffset:(CGPoint)contentOffset velocity:(CGPoint)velocity -{ - if (ASDisplayShouldFetchBatchForScrollView(self, self.scrollDirection, ASScrollDirectionVerticalDirections, contentOffset, velocity)) { - [self _beginBatchFetching]; - } -} - -- (void)_beginBatchFetching -{ - [_batchContext beginBatchFetching]; - if (_asyncDelegateFlags.tableNodeWillBeginBatchFetch) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - GET_TABLENODE_OR_RETURN(tableNode, (void)0); - [_asyncDelegate tableNode:tableNode willBeginBatchFetchWithContext:_batchContext]; - }); - } else if (_asyncDelegateFlags.tableViewWillBeginBatchFetch) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate tableView:self willBeginBatchFetchWithContext:_batchContext]; -#pragma clang diagnostic pop - }); - } -} - -#pragma mark - ASRangeControllerDataSource - -- (ASRangeController *)rangeController -{ - return _rangeController; -} - -- (NSHashTable *)visibleElementsForRangeController:(ASRangeController *)rangeController -{ - return ASPointerTableByFlatMapping(_visibleElements, id element, element); -} - -- (ASScrollDirection)scrollDirectionForRangeController:(ASRangeController *)rangeController -{ - return self.scrollDirection; -} - -- (ASInterfaceState)interfaceStateForRangeController:(ASRangeController *)rangeController -{ - return ASInterfaceStateForDisplayNode(self.tableNode, self.window); -} - -- (NSString *)nameForRangeControllerDataSource -{ - return self.asyncDataSource ? NSStringFromClass([self.asyncDataSource class]) : NSStringFromClass([self class]); -} - -#pragma mark - ASRangeControllerDelegate - -- (BOOL)rangeControllerShouldUpdateRanges:(ASRangeController *)rangeController -{ - return YES; -} - -- (void)rangeController:(ASRangeController *)rangeController updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet updates:(dispatch_block_t)updates -{ - ASDisplayNodeAssertMainThread(); - if (!self.asyncDataSource || _updatingInResponseToInteractiveMove) { - updates(); - [changeSet executeCompletionHandlerWithFinished:NO]; - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes - } - - if (changeSet.includesReloadData) { - LOG(@"UITableView reloadData"); - ASPerformBlockWithoutAnimation(!changeSet.animated, ^{ - if (self.test_enableSuperUpdateCallLogging) { - NSLog(@"-[super reloadData]"); - } - updates(); - [super reloadData]; - // Flush any range changes that happened as part of submitting the reload. - [_rangeController updateIfNeeded]; - [self _scheduleCheckForBatchFetchingForNumberOfChanges:1]; - [changeSet executeCompletionHandlerWithFinished:YES]; - }); - return; - } - - BOOL shouldAdjustContentOffset = (_automaticallyAdjustsContentOffset && !changeSet.includesReloadData); - if (shouldAdjustContentOffset) { - [self beginAdjustingContentOffset]; - } - - NSUInteger numberOfUpdates = 0; - - LOG(@"--- UITableView beginUpdates"); - [super beginUpdates]; - - updates(); - - for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeReload]) { - NSArray *indexPaths = change.indexPaths; - UITableViewRowAnimation animationOptions = (UITableViewRowAnimation)change.animationOptions; - - LOG(@"UITableView reloadRows:%ld rows", indexPaths.count); - BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; - ASPerformBlockWithoutAnimation(preventAnimation, ^{ - if (self.test_enableSuperUpdateCallLogging) { - NSLog(@"-[super reloadRowsAtIndexPaths]: %@", indexPaths); - } - [super reloadRowsAtIndexPaths:indexPaths withRowAnimation:animationOptions]; - }); - - numberOfUpdates++; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeReload]) { - NSIndexSet *sectionIndexes = change.indexSet; - UITableViewRowAnimation animationOptions = (UITableViewRowAnimation)change.animationOptions; - - LOG(@"UITableView reloadSections:%@", sectionIndexes); - BOOL preventAnimation = (animationOptions == UITableViewRowAnimationNone); - ASPerformBlockWithoutAnimation(preventAnimation, ^{ - if (self.test_enableSuperUpdateCallLogging) { - NSLog(@"-[super reloadSections]: %@", sectionIndexes); - } - [super reloadSections:sectionIndexes withRowAnimation:animationOptions]; - }); - - numberOfUpdates++; - } - - for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeOriginalDelete]) { - NSArray *indexPaths = change.indexPaths; - UITableViewRowAnimation animationOptions = (UITableViewRowAnimation)change.animationOptions; - - LOG(@"UITableView deleteRows:%ld rows", indexPaths.count); - BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; - ASPerformBlockWithoutAnimation(preventAnimation, ^{ - if (self.test_enableSuperUpdateCallLogging) { - NSLog(@"-[super deleteRowsAtIndexPaths]: %@", indexPaths); - } - [super deleteRowsAtIndexPaths:indexPaths withRowAnimation:animationOptions]; - }); - - numberOfUpdates++; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeOriginalDelete]) { - NSIndexSet *sectionIndexes = change.indexSet; - UITableViewRowAnimation animationOptions = (UITableViewRowAnimation)change.animationOptions; - - LOG(@"UITableView deleteSections:%@", sectionIndexes); - BOOL preventAnimation = (animationOptions == UITableViewRowAnimationNone); - ASPerformBlockWithoutAnimation(preventAnimation, ^{ - if (self.test_enableSuperUpdateCallLogging) { - NSLog(@"-[super deleteSections]: %@", sectionIndexes); - } - [super deleteSections:sectionIndexes withRowAnimation:animationOptions]; - }); - - numberOfUpdates++; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeOriginalInsert]) { - NSIndexSet *sectionIndexes = change.indexSet; - UITableViewRowAnimation animationOptions = (UITableViewRowAnimation)change.animationOptions; - - LOG(@"UITableView insertSections:%@", sectionIndexes); - BOOL preventAnimation = (animationOptions == UITableViewRowAnimationNone); - ASPerformBlockWithoutAnimation(preventAnimation, ^{ - if (self.test_enableSuperUpdateCallLogging) { - NSLog(@"-[super insertSections]: %@", sectionIndexes); - } - [super insertSections:sectionIndexes withRowAnimation:animationOptions]; - }); - - numberOfUpdates++; - } - - for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeOriginalInsert]) { - NSArray *indexPaths = change.indexPaths; - UITableViewRowAnimation animationOptions = (UITableViewRowAnimation)change.animationOptions; - - LOG(@"UITableView insertRows:%ld rows", indexPaths.count); - BOOL preventAnimation = (animationOptions == UITableViewRowAnimationNone); - ASPerformBlockWithoutAnimation(preventAnimation, ^{ - if (self.test_enableSuperUpdateCallLogging) { - NSLog(@"-[super insertRowsAtIndexPaths]: %@", indexPaths); - } - [super insertRowsAtIndexPaths:indexPaths withRowAnimation:animationOptions]; - }); - - numberOfUpdates++; - } - - LOG(@"--- UITableView endUpdates"); - ASPerformBlockWithoutAnimation(!changeSet.animated, ^{ - [super endUpdates]; - [_rangeController updateIfNeeded]; - [self _scheduleCheckForBatchFetchingForNumberOfChanges:numberOfUpdates]; - }); - if (shouldAdjustContentOffset) { - [self endAdjustingContentOffsetAnimated:changeSet.animated]; - } - [changeSet executeCompletionHandlerWithFinished:YES]; -} - -#pragma mark - ASDataControllerSource - -- (BOOL)dataController:(ASDataController *)dataController shouldEagerlyLayoutNode:(ASCellNode *)node -{ - return YES; -} - -- (BOOL)dataControllerShouldSerializeNodeCreation:(ASDataController *)dataController -{ - return NO; -} - -- (BOOL)dataController:(ASDataController *)dataController shouldSynchronouslyProcessChangeSet:(_ASHierarchyChangeSet *)changeSet -{ - // Reload data is expensive, don't block main while doing so. - if (changeSet.includesReloadData) { - return NO; - } - // For more details on this method, see the comment in the ASCollectionView implementation. - if (changeSet.countForAsyncLayout < 2) { - return YES; - } - CGSize contentSize = self.contentSize; - CGSize boundsSize = self.bounds.size; - if (contentSize.height <= boundsSize.height && contentSize.width <= boundsSize.width) { - return YES; - } - return NO; -} - -- (void)dataControllerDidFinishWaiting:(ASDataController *)dataController -{ - // ASCellLayoutMode is not currently supported on ASTableView (see ASCollectionView for details). -} - -- (id)dataController:(ASDataController *)dataController nodeModelForItemAtIndexPath:(NSIndexPath *)indexPath -{ - // Not currently supported for tables. Will be added when the collection API stabilizes. - return nil; -} - -- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath shouldAsyncLayout:(BOOL *)shouldAsyncLayout -{ - ASCellNodeBlock block = nil; - - if (_asyncDataSourceFlags.tableNodeNodeBlockForRow) { - if (ASTableNode *tableNode = self.tableNode) { - block = [_asyncDataSource tableNode:tableNode nodeBlockForRowAtIndexPath:indexPath]; - } - } else if (_asyncDataSourceFlags.tableNodeNodeForRow) { - ASCellNode *node = nil; - if (ASTableNode *tableNode = self.tableNode) { - node = [_asyncDataSource tableNode:tableNode nodeForRowAtIndexPath:indexPath]; - } - if ([node isKindOfClass:[ASCellNode class]]) { - block = ^{ - return node; - }; - } else { - ASDisplayNodeFailAssert(@"Data source returned invalid node from tableNode:nodeForRowAtIndexPath:. Node: %@", node); - } - } else if (_asyncDataSourceFlags.tableViewNodeBlockForRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - block = [_asyncDataSource tableView:self nodeBlockForRowAtIndexPath:indexPath]; - } else if (_asyncDataSourceFlags.tableViewNodeForRow) { - ASCellNode *node = [_asyncDataSource tableView:self nodeForRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop - if ([node isKindOfClass:[ASCellNode class]]) { - block = ^{ - return node; - }; - } else { - ASDisplayNodeFailAssert(@"Data source returned invalid node from tableView:nodeForRowAtIndexPath:. Node: %@", node); - } - } - - // Handle nil node block - if (block == nil) { - ASDisplayNodeFailAssert(@"ASTableNode could not get a node block for row at index path %@", indexPath); - block = ^{ - return [[ASCellNode alloc] init]; - }; - } - - // Wrap the node block - __weak __typeof__(self) weakSelf = self; - return ^{ - __typeof__(self) strongSelf = weakSelf; - ASCellNode *node = (block != nil ? block() : [[ASCellNode alloc] init]); - ASDisplayNodeAssert([node isKindOfClass:[ASCellNode class]], @"ASTableNode provided a non-ASCellNode! %@, %@", node, strongSelf); - - [node enterHierarchyState:ASHierarchyStateRangeManaged]; - if (node.interactionDelegate == nil) { - node.interactionDelegate = strongSelf; - } - if (_inverted) { - node.transform = CATransform3DMakeScale(1, -1, 1) ; - } - return node; - }; - return block; -} - -- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath -{ - ASSizeRange constrainedSize = ASSizeRangeZero; - if (_asyncDelegateFlags.tableNodeConstrainedSizeForRow) { - GET_TABLENODE_OR_RETURN(tableNode, constrainedSize); - ASSizeRange delegateConstrainedSize = [_asyncDelegate tableNode:tableNode constrainedSizeForRowAtIndexPath:indexPath]; - // ignore widths in the returned size range (for TableView) - constrainedSize = ASSizeRangeMake(CGSizeMake(_nodesConstrainedWidth, delegateConstrainedSize.min.height), - CGSizeMake(_nodesConstrainedWidth, delegateConstrainedSize.max.height)); - } else if (_asyncDelegateFlags.tableViewConstrainedSizeForRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - ASSizeRange delegateConstrainedSize = [_asyncDelegate tableView:self constrainedSizeForRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop - // ignore widths in the returned size range (for TableView) - constrainedSize = ASSizeRangeMake(CGSizeMake(_nodesConstrainedWidth, delegateConstrainedSize.min.height), - CGSizeMake(_nodesConstrainedWidth, delegateConstrainedSize.max.height)); - } else { - constrainedSize = ASSizeRangeMake(CGSizeMake(_nodesConstrainedWidth, 0), - CGSizeMake(_nodesConstrainedWidth, CGFLOAT_MAX)); - } - return constrainedSize; -} - -- (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section -{ - if (_asyncDataSourceFlags.tableNodeNumberOfRowsInSection) { - GET_TABLENODE_OR_RETURN(tableNode, 0); - return [_asyncDataSource tableNode:tableNode numberOfRowsInSection:section]; - } else if (_asyncDataSourceFlags.tableViewNumberOfRowsInSection) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDataSource tableView:self numberOfRowsInSection:section]; -#pragma clang diagnostic pop - } else { - return 0; - } -} - -- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController -{ - if (_asyncDataSourceFlags.numberOfSectionsInTableNode) { - GET_TABLENODE_OR_RETURN(tableNode, 0); - return [_asyncDataSource numberOfSectionsInTableNode:tableNode]; - } else if (_asyncDataSourceFlags.numberOfSectionsInTableView) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDataSource numberOfSectionsInTableView:self]; -#pragma clang diagnostic pop - } else { - return 1; // default section number - } -} - -- (BOOL)dataController:(ASDataController *)dataController presentedSizeForElement:(ASCollectionElement *)element matchesSize:(CGSize)size -{ - NSIndexPath *indexPath = [self indexPathForNode:element.node]; - if (indexPath == nil) { - ASDisplayNodeFailAssert(@"Data controller should not ask for presented size for element that is not presented."); - return YES; - } - CGRect rect = [self rectForRowAtIndexPath:indexPath]; - -#if TARGET_OS_IOS - /** - * Weirdly enough, Apple expects the return value in tableView:heightForRowAtIndexPath: to _include_ the height - * of the separator, if there is one! So if rectForRow would return 44.0 we need to use 43.5. - */ - if (self.separatorStyle != UITableViewCellSeparatorStyleNone) { - rect.size.height -= 1.0 / ASScreenScale(); - } -#endif - - return (fabs(rect.size.height - size.height) < FLT_EPSILON); -} - -#pragma mark - _ASTableViewCellDelegate - -- (void)didLayoutSubviewsOfTableViewCell:(_ASTableViewCell *)tableViewCell -{ - ASCellNode *node = tableViewCell.node; - if (node == nil || _asyncDataSource == nil) { - return; - } - - CGFloat contentViewWidth = tableViewCell.contentView.bounds.size.width; - ASSizeRange constrainedSize = node.constrainedSizeForCalculatedLayout; - - // Table view cells should always fill its content view width. - // Normally the content view width equals to the constrained size width (which equals to the table view width). - // If there is a mismatch between these values, for example after the table view entered or left editing mode, - // content view width is preferred and used to re-measure the cell node. - if (CGSizeEqualToSize(node.calculatedSize, CGSizeZero) == NO && contentViewWidth != constrainedSize.max.width) { - constrainedSize.min.width = contentViewWidth; - constrainedSize.max.width = contentViewWidth; - - // Re-measurement is done on main to ensure thread affinity. In the worst case, this is as fast as UIKit's implementation. - // - // Unloaded nodes *could* be re-measured off the main thread, but only with the assumption that content view width - // is the same for all cells (because there is no easy way to get that individual value before the node being assigned to a _ASTableViewCell). - // Also, in many cases, some nodes may not need to be re-measured at all, such as when user enters and then immediately leaves editing mode. - // To avoid premature optimization and making such assumption, as well as to keep ASTableView simple, re-measurement is strictly done on main. - CGSize oldSize = node.bounds.size; - const CGSize calculatedSize = [node layoutThatFits:constrainedSize].size; - node.frame = { .size = calculatedSize }; - - // After the re-measurement, set the new constrained size to the node's backing colleciton element. - node.collectionElement.constrainedSize = constrainedSize; - - // If the node height changed, trigger a height requery. - if (oldSize.height != calculatedSize.height) { - [self beginUpdates]; - [self endUpdatesAnimated:(ASDisplayNodeLayerHasAnimations(self.layer) == NO) completion:nil]; - } - } -} - -#pragma mark - ASCellNodeDelegate - -- (void)nodeSelectedStateDidChange:(ASCellNode *)node -{ - NSIndexPath *indexPath = [self indexPathForNode:node]; - if (indexPath) { - if (node.isSelected) { - [self selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone]; - } else { - [self deselectRowAtIndexPath:indexPath animated:NO]; - } - } -} - -- (void)nodeHighlightedStateDidChange:(ASCellNode *)node -{ - NSIndexPath *indexPath = [self indexPathForNode:node]; - if (indexPath) { - [self cellForRowAtIndexPath:indexPath].highlighted = node.isHighlighted; - } -} - -- (void)nodeDidInvalidateSize:(ASCellNode *)node -{ - [_cellsForLayoutUpdates addObject:node]; - [self setNeedsLayout]; -} - -// Cause UITableView to requery for the new height of this node -- (void)requeryNodeHeights -{ - _queuedNodeHeightUpdate = NO; - - [super beginUpdates]; - [super endUpdates]; -} - -#pragma mark - Helper Methods - -// Note: This is called every layout, and so it is very performance sensitive. -- (CGFloat)sectionIndexWidth -{ - // If they don't implement the methods, then there's no section index. - if (_asyncDataSourceFlags.sectionIndexMethods == NO) { - return 0; - } - - UIView *indexView = _sectionIndexView; - if (indexView.superview == self) { - return indexView.frame.size.width; - } - - CGRect bounds = self.bounds; - for (UIView *view in self.subviews) { - CGRect frame = view.frame; - // Section index is right-aligned and less than half-width. - if (CGRectGetMaxX(frame) == CGRectGetMaxX(bounds) && frame.size.width * 2 < bounds.size.width) { - _sectionIndexView = view; - return frame.size.width; - } - } - return 0; -} - -#pragma mark - _ASDisplayView behavior substitutions -// Need these to drive interfaceState so we know when we are visible, if not nested in another range-managing element. -// Because our superclass is a true UIKit class, we cannot also subclass _ASDisplayView. -- (void)willMoveToWindow:(UIWindow *)newWindow -{ - BOOL visible = (newWindow != nil); - ASDisplayNode *node = self.tableNode; - if (visible && !node.inHierarchy) { - [node __enterHierarchy]; - } -} - -- (void)didMoveToWindow -{ - BOOL visible = (self.window != nil); - ASDisplayNode *node = self.tableNode; - BOOL rangeControllerNeedsUpdate = ![node supportsRangeManagedInterfaceState];; - - if (!visible && node.inHierarchy) { - if (rangeControllerNeedsUpdate) { - rangeControllerNeedsUpdate = NO; - // Exit CellNodes first before Table to match UIKit behaviors (tear down bottom up). - // Although we have not yet cleared the interfaceState's Visible bit (this happens in __exitHierarchy), - // the ASRangeController will get the correct value from -interfaceStateForRangeController:. - [_rangeController updateRanges]; - } - [node __exitHierarchy]; - } - - // Updating the visible node index paths only for not range managed nodes. Range managed nodes will get their - // their update in the layout pass - if (rangeControllerNeedsUpdate) { - [_rangeController updateRanges]; - } - - // When we aren't visible, we will only fetch up to the visible area. Now that we are visible, - // we will fetch visible area + leading screens, so we need to check. - if (visible) { - [self _checkForBatchFetching]; - } -} - -- (void)willMoveToSuperview:(UIView *)newSuperview -{ - if (self.superview == nil && newSuperview != nil) { - _keepalive_node = self.tableNode; - } -} - -- (void)didMoveToSuperview -{ - if (self.superview == nil) { - _keepalive_node = nil; - } -} - -#pragma mark - Accessibility overrides - -- (NSArray *)accessibilityElements -{ - [self waitUntilAllUpdatesAreCommitted]; - return [super accessibilityElements]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASTableViewInternal.h b/submodules/AsyncDisplayKit/Source/ASTableViewInternal.h deleted file mode 100644 index b887e9323b..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTableViewInternal.h +++ /dev/null @@ -1,69 +0,0 @@ -// -// ASTableViewInternal.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -@class ASDataController; -@class ASTableNode; -@class ASRangeController; -@class ASEventLog; - -@interface ASTableView (Internal) - -@property (nonatomic, readonly) ASDataController *dataController; -@property (nonatomic, weak) ASTableNode *tableNode; -@property (nonatomic, readonly) ASRangeController *rangeController; - -/** - * Initializer. - * - * @param frame A rectangle specifying the initial location and size of the table view in its superview’€™s coordinates. - * The frame of the table view changes as table cells are added and deleted. - * - * @param style A constant that specifies the style of the table view. See UITableViewStyle for descriptions of valid constants. - * - * @param dataControllerClass A controller class injected to and used to create a data controller for the table view. - * - * @param eventLog An event log passed through to the data controller. - */ -- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass owningNode:(ASTableNode *)tableNode eventLog:(ASEventLog *)eventLog; - -/// Set YES and we'll log every time we call [super insertRows…] etc -@property (nonatomic) BOOL test_enableSuperUpdateCallLogging; - -/** - * Attempt to get the view-layer index path for the row with the given index path. - * - * @param indexPath The index path of the row. - * @param wait If the item hasn't reached the view yet, this attempts to wait for updates to commit. - */ -- (NSIndexPath *)convertIndexPathFromTableNode:(NSIndexPath *)indexPath waitingIfNeeded:(BOOL)wait; - -/** - * Attempt to get the node index path given the view-layer index path. - * - * @param indexPath The index path of the row. - */ -- (NSIndexPath *)convertIndexPathToTableNode:(NSIndexPath *)indexPath; - -/** - * Attempt to get the node index paths given the view-layer index paths. - * - * @param indexPaths An array of index paths in the view space - */ -- (NSArray *)convertIndexPathsToTableNode:(NSArray *)indexPaths; - -/// Returns the width of the section index view on the right-hand side of the table, if one is present. -- (CGFloat)sectionIndexWidth; - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASTableViewProtocols.h b/submodules/AsyncDisplayKit/Source/ASTableViewProtocols.h deleted file mode 100644 index 56de956724..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTableViewProtocols.h +++ /dev/null @@ -1,98 +0,0 @@ -// -// ASTableViewProtocols.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * This is a subset of UITableViewDataSource. - * - * @see ASTableDataSource - */ -@protocol ASCommonTableDataSource - -@optional - -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:numberOfRowsInSection: instead."); - -- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView ASDISPLAYNODE_DEPRECATED_MSG("Implement numberOfSectionsInTableNode: instead."); - -- (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section; -- (nullable NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section; - -- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath; - -- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath; - -- (nullable NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView; -- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index; - -- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath; - -- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath; - -@end - - -/** - * This is a subset of UITableViewDelegate. - * - * @see ASTableDelegate - */ -@protocol ASCommonTableViewDelegate - -@optional - -- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section; -- (void)tableView:(UITableView *)tableView willDisplayFooterView:(UIView *)view forSection:(NSInteger)section; -- (void)tableView:(UITableView *)tableView didEndDisplayingHeaderView:(UIView *)view forSection:(NSInteger)section; -- (void)tableView:(UITableView *)tableView didEndDisplayingFooterView:(UIView *)view forSection:(NSInteger)section; - -- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section; -- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section; - -- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section; -- (nullable UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section; - -- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath; - -- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:shouldHighlightRowAtIndexPath: instead."); -- (void)tableView:(UITableView *)tableView didHighlightRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:didHighlightRowAtIndexPath: instead."); -- (void)tableView:(UITableView *)tableView didUnhighlightRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:didUnhighlightRowAtIndexPath: instead."); - -- (nullable NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:willSelectRowAtIndexPath: instead."); -- (nullable NSIndexPath *)tableView:(UITableView *)tableView willDeselectRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:willDeselectRowAtIndexPath: instead."); -- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:didSelectRowAtIndexPath: instead."); -- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:didDeselectRowAtIndexPath: instead."); - -- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath; -- (nullable NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath; -#if TARGET_OS_IOS -- (nullable NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath; -#endif -- (BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath; - -- (void)tableView:(UITableView*)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath; -- (void)tableView:(UITableView*)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath; - -- (NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath; - -- (NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath; - -- (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:shouldShowMenuForRowAtIndexPath: instead."); -- (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:canPerformAction:forRowAtIndexPath:withSender: instead."); -- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:performAction:forRowAtIndexPath:withSender: instead."); - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitComponents.mm b/submodules/AsyncDisplayKit/Source/ASTextKitComponents.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/TextKit/ASTextKitComponents.mm rename to submodules/AsyncDisplayKit/Source/ASTextKitComponents.mm diff --git a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitContext.h b/submodules/AsyncDisplayKit/Source/ASTextKitContext.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/TextKit/ASTextKitContext.h rename to submodules/AsyncDisplayKit/Source/ASTextKitContext.h diff --git a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitContext.mm b/submodules/AsyncDisplayKit/Source/ASTextKitContext.mm similarity index 96% rename from submodules/AsyncDisplayKit/Source/TextKit/ASTextKitContext.mm rename to submodules/AsyncDisplayKit/Source/ASTextKitContext.mm index 8883f3175f..42f4a1a45c 100644 --- a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitContext.mm +++ b/submodules/AsyncDisplayKit/Source/ASTextKitContext.mm @@ -7,11 +7,11 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import +#import "ASTextKitContext.h" #if AS_ENABLE_TEXTNODE -#import +#import "ASLayoutManager.h" #import #include diff --git a/submodules/AsyncDisplayKit/Source/ASTextNode+Beta.h b/submodules/AsyncDisplayKit/Source/ASTextNode+Beta.h deleted file mode 100644 index ad897c5f0e..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTextNode+Beta.h +++ /dev/null @@ -1,45 +0,0 @@ -// -// ASTextNode+Beta.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface ASTextNode () - -/** - @abstract An array of descending scale factors that will be applied to this text node to try to make it fit within its constrained size - @discussion This array should be in descending order and NOT contain the scale factor 1.0. For example, it could return @[@(.9), @(.85), @(.8)]; - @default nil (no scaling) - */ -@property (nullable, nonatomic, copy) NSArray *pointSizeScaleFactors; - -/** - @abstract Text margins for text laid out in the text node. - @discussion defaults to UIEdgeInsetsZero. - This property can be useful for handling text which does not fit within the view by default. An example: like UILabel, - ASTextNode will clip the left and right of the string "judar" if it's rendered in an italicised font. - */ -@property (nonatomic) UIEdgeInsets textContainerInset; - -/** - * Returns YES if this node is using the experimental implementation. NO otherwise. Will not change. - */ -@property (readonly) BOOL usingExperiment; - -/** - * Returns a Boolean indicating if the text node will truncate for the given constrained size - */ -- (BOOL)shouldTruncateForConstrainedSize:(ASSizeRange)constrainedSize; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASTextNode.h b/submodules/AsyncDisplayKit/Source/ASTextNode.h deleted file mode 100644 index 783eafc53e..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTextNode.h +++ /dev/null @@ -1,264 +0,0 @@ -// -// ASTextNode.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import -#import - -#if (!AS_ENABLE_TEXTNODE) - -// Pull in ASTextNode2 to replace ASTextNode with ASTextNode2 -#import - -#else - -NS_ASSUME_NONNULL_BEGIN - -/** - @abstract Draws interactive rich text. - @discussion Backed by TextKit. - */ -@interface ASTextNode : ASControlNode - -/** - @abstract The styled text displayed by the node. - @discussion Defaults to nil, no text is shown. - For inline image attachments, add an attribute of key NSAttachmentAttributeName, with a value of an NSTextAttachment. - */ -@property (nullable, copy) NSAttributedString *attributedText; - -#pragma mark - Truncation - -/** - @abstract The attributedText to use when the text must be truncated. - @discussion Defaults to a localized ellipsis character. - */ -@property (nullable, copy) NSAttributedString *truncationAttributedText; - -/** - @summary The second attributed string appended for truncation. - @discussion This string will be highlighted on touches. - @default nil - */ -@property (nullable, copy) NSAttributedString *additionalTruncationMessage; - -/** - @abstract Determines how the text is truncated to fit within the receiver's maximum size. - @discussion Defaults to NSLineBreakByWordWrapping. - @note Setting a truncationMode in attributedString will override the truncation mode set here. - */ -@property NSLineBreakMode truncationMode; - -/** - @abstract If the text node is truncated. Text must have been sized first. - */ -@property (readonly, getter=isTruncated) BOOL truncated; - -/** - @abstract The maximum number of lines to render of the text before truncation. - @default 0 (No limit) - */ -@property NSUInteger maximumNumberOfLines; - -/** - @abstract The number of lines in the text. Text must have been sized first. - */ -@property (readonly) NSUInteger lineCount; - -/** - * An array of path objects representing the regions where text should not be displayed. - * - * @discussion The default value of this property is an empty array. You can - * assign an array of UIBezierPath objects to exclude text from one or more regions in - * the text node's bounds. You can use this property to have text wrap around images, - * shapes or other text like a fancy magazine. - */ -@property (nullable, copy) NSArray *exclusionPaths; - -#pragma mark - Placeholders - -/** - * @abstract ASTextNode has a special placeholder behavior when placeholderEnabled is YES. - * - * @discussion Defaults to NO. When YES, it draws rectangles for each line of text, - * following the true shape of the text's wrapping. This visually mirrors the overall - * shape and weight of paragraphs, making the appearance of the finished text less jarring. - */ -@property BOOL placeholderEnabled; - -/** - @abstract The placeholder color. - */ -@property (nullable, copy) UIColor *placeholderColor; - -/** - @abstract Inset each line of the placeholder. - */ -@property UIEdgeInsets placeholderInsets; - -#pragma mark - Shadow - -/** - @abstract When you set these ASDisplayNode properties, they are composited into the bitmap instead of being applied by CA. - - @property (nonatomic) CGColorRef shadowColor; - @property (nonatomic) CGFloat shadowOpacity; - @property (nonatomic) CGSize shadowOffset; - @property (nonatomic) CGFloat shadowRadius; - */ - -/** - @abstract The number of pixels used for shadow padding on each side of the receiver. - @discussion Each inset will be less than or equal to zero, so that applying - UIEdgeInsetsRect(boundingRectForText, shadowPadding) - will return a CGRect large enough to fit both the text and the appropriate shadow padding. - */ -@property (readonly) UIEdgeInsets shadowPadding; - -#pragma mark - Positioning - -/** - @abstract Returns an array of rects bounding the characters in a given text range. - @param textRange A range of text. Must be valid for the receiver's string. - @discussion Use this method to detect all the different rectangles a given range of text occupies. - The rects returned are not guaranteed to be contiguous (for example, if the given text range spans - a line break, the rects returned will be on opposite sides and different lines). The rects returned - are in the coordinate system of the receiver. - */ -- (NSArray *)rectsForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT; - -/** - @abstract Returns an array of rects used for highlighting the characters in a given text range. - @param textRange A range of text. Must be valid for the receiver's string. - @discussion Use this method to detect all the different rectangles the highlights of a given range of text occupies. - The rects returned are not guaranteed to be contiguous (for example, if the given text range spans - a line break, the rects returned will be on opposite sides and different lines). The rects returned - are in the coordinate system of the receiver. This method is useful for visual coordination with a - highlighted range of text. - */ -- (NSArray *)highlightRectsForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT; - -/** - @abstract Returns a bounding rect for the given text range. - @param textRange A range of text. Must be valid for the receiver's string. - @discussion The height of the frame returned is that of the receiver's line-height; adjustment for - cap-height and descenders is not performed. This method raises an exception if textRange is not - a valid substring range of the receiver's string. - */ -- (CGRect)frameForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT; - -/** - @abstract Returns the trailing rectangle of space in the receiver, after the final character. - @discussion Use this method to detect which portion of the receiver is not occupied by characters. - The rect returned is in the coordinate system of the receiver. - */ -- (CGRect)trailingRect AS_WARN_UNUSED_RESULT; - - -#pragma mark - Actions - -/** - @abstract The set of attribute names to consider links. Defaults to NSLinkAttributeName. - */ -@property (copy) NSArray *linkAttributeNames; - -/** - @abstract Indicates whether the receiver has an entity at a given point. - @param point The point, in the receiver's coordinate system. - @param attributeNameOut The name of the attribute at the point. Can be NULL. - @param rangeOut The ultimate range of the found text. Can be NULL. - @result YES if an entity exists at `point`; NO otherwise. - */ -- (nullable id)linkAttributeValueAtPoint:(CGPoint)point attributeName:(out NSString * _Nullable * _Nullable)attributeNameOut range:(out NSRange * _Nullable)rangeOut AS_WARN_UNUSED_RESULT; - -/** - @abstract The style to use when highlighting text. - */ -@property ASTextNodeHighlightStyle highlightStyle; - -/** - @abstract The range of text highlighted by the receiver. Changes to this property are not animated by default. - */ -@property NSRange highlightRange; - -/** - @abstract Set the range of text to highlight, with optional animation. - - @param highlightRange The range of text to highlight. - - @param animated Whether the text should be highlighted with an animation. - */ -- (void)setHighlightRange:(NSRange)highlightRange animated:(BOOL)animated; - -/** - @abstract Responds to actions from links in the text node. - @discussion The delegate must be set before the node is loaded, and implement - textNode:longPressedLinkAttribute:value:atPoint:textRange: in order for - the long press gesture recognizer to be installed. - */ -@property (nullable, weak) id delegate; - -/** - @abstract If YES and a long press is recognized, touches are cancelled. Default is NO - */ -@property (nonatomic) BOOL longPressCancelsTouches; - -/** - @abstract if YES will not intercept touches for non-link areas of the text. Default is NO. - */ -@property (nonatomic) BOOL passthroughNonlinkTouches; - -@end - -@interface ASTextNode (Unavailable) - -- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_UNAVAILABLE; - -- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_UNAVAILABLE; - -@end - -/** - * @abstract Text node unsupported properties - */ -@interface ASTextNode (Unsupported) - -@property (nullable, nonatomic) id textContainerLinePositionModifier; - -@end - -/** - * @abstract Text node deprecated properties - */ -@interface ASTextNode (Deprecated) - -/** - The attributedString and attributedText properties are equivalent, but attributedText is now the standard API - name in order to match UILabel and ASEditableTextNode. - - @see attributedText - */ -@property (nullable, copy) NSAttributedString *attributedString ASDISPLAYNODE_DEPRECATED_MSG("Use .attributedText instead."); - - -/** - The truncationAttributedString and truncationAttributedText properties are equivalent, but truncationAttributedText is now the - standard API name in order to match UILabel and ASEditableTextNode. - - @see truncationAttributedText - */ -@property (nullable, copy) NSAttributedString *truncationAttributedString ASDISPLAYNODE_DEPRECATED_MSG("Use .truncationAttributedText instead."); - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASTextNode.mm b/submodules/AsyncDisplayKit/Source/ASTextNode.mm deleted file mode 100644 index e872a09297..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTextNode.mm +++ /dev/null @@ -1,1494 +0,0 @@ -// -// ASTextNode.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_ENABLE_TEXTNODE - -#import - -#import - -#import -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#import -#import -#import - -#import -#import - -#import -#import -#import -#import - -/** - * If set, we will record all values set to attributedText into an array - * and once we get 2000, we'll write them all out into a plist file. - * - * This is useful for gathering realistic text data sets from apps for performance - * testing. - */ -#define AS_TEXTNODE_RECORD_ATTRIBUTED_STRINGS 0 - -static const NSTimeInterval ASTextNodeHighlightFadeOutDuration = 0.15; -static const NSTimeInterval ASTextNodeHighlightFadeInDuration = 0.1; -static const CGFloat ASTextNodeHighlightLightOpacity = 0.11; -static const CGFloat ASTextNodeHighlightDarkOpacity = 0.22; -static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncationAttribute"; - -#pragma mark - ASTextKitRenderer - -@interface ASTextNodeRendererKey : NSObject -@property (nonatomic) ASTextKitAttributes attributes; -@property (nonatomic) CGSize constrainedSize; -@end - -@implementation ASTextNodeRendererKey { - std::mutex _m; -} - -- (NSUInteger)hash -{ - std::lock_guard _l(_m); -#pragma clang diagnostic push -#pragma clang diagnostic warning "-Wpadded" - struct { - size_t attributesHash; - CGSize constrainedSize; -#pragma clang diagnostic pop - } data = { - _attributes.hash(), - _constrainedSize - }; - return ASHashBytes(&data, sizeof(data)); -} - -- (BOOL)isEqual:(ASTextNodeRendererKey *)object -{ - if (self == object) { - return YES; - } - - // NOTE: Skip the class check for this specialized, internal Key object. - - // Lock both objects, avoiding deadlock. - std::lock(_m, object->_m); - std::lock_guard lk1(_m, std::adopt_lock); - std::lock_guard lk2(object->_m, std::adopt_lock); - - return _attributes == object->_attributes && CGSizeEqualToSize(_constrainedSize, object->_constrainedSize); -} - -@end - -static NSCache *sharedRendererCache() -{ - static dispatch_once_t onceToken; - static NSCache *__rendererCache = nil; - dispatch_once(&onceToken, ^{ - __rendererCache = [[NSCache alloc] init]; - __rendererCache.countLimit = 500; // 500 renders cache - }); - return __rendererCache; -} - -/** - The concept here is that neither the node nor layout should ever have a strong reference to the renderer object. - This is to reduce memory load when loading thousands and thousands of text nodes into memory at once. Instead - we maintain a LRU renderer cache that is queried via a unique key based on text kit attributes and constrained size. - */ - -static ASTextKitRenderer *rendererForAttributes(ASTextKitAttributes attributes, CGSize constrainedSize) -{ - NSCache *cache = sharedRendererCache(); - - ASTextNodeRendererKey *key = [[ASTextNodeRendererKey alloc] init]; - key.attributes = attributes; - key.constrainedSize = constrainedSize; - - ASTextKitRenderer *renderer = [cache objectForKey:key]; - if (renderer == nil) { - renderer = [[ASTextKitRenderer alloc] initWithTextKitAttributes:attributes constrainedSize:constrainedSize]; - [cache setObject:renderer forKey:key]; - } - - return renderer; -} - -#pragma mark - ASTextNodeDrawParameter - -@interface ASTextNodeDrawParameter : NSObject { -@package - ASTextKitAttributes _rendererAttributes; - UIColor *_backgroundColor; - UIEdgeInsets _textContainerInsets; - CGFloat _contentScale; - BOOL _opaque; - CGRect _bounds; -} -@end - -@implementation ASTextNodeDrawParameter - -- (instancetype)initWithRendererAttributes:(ASTextKitAttributes)rendererAttributes - backgroundColor:(/*nullable*/ UIColor *)backgroundColor - textContainerInsets:(UIEdgeInsets)textContainerInsets - contentScale:(CGFloat)contentScale - opaque:(BOOL)opaque - bounds:(CGRect)bounds -{ - self = [super init]; - if (self != nil) { - _rendererAttributes = rendererAttributes; - _backgroundColor = backgroundColor; - _textContainerInsets = textContainerInsets; - _contentScale = contentScale; - _opaque = opaque; - _bounds = bounds; - } - return self; -} - -- (ASTextKitRenderer *)rendererForBounds:(CGRect)bounds -{ - CGRect rect = UIEdgeInsetsInsetRect(bounds, _textContainerInsets); - return rendererForAttributes(_rendererAttributes, rect.size); -} - -@end - - -#pragma mark - ASTextNode - -@interface ASTextNode () - -@end - -@implementation ASTextNode { - CGSize _shadowOffset; - CGColorRef _shadowColor; - UIColor *_cachedShadowUIColor; - UIColor *_placeholderColor; - CGFloat _shadowOpacity; - CGFloat _shadowRadius; - - UIEdgeInsets _textContainerInset; - - NSArray *_exclusionPaths; - - NSAttributedString *_attributedText; - NSAttributedString *_truncationAttributedText; - NSAttributedString *_additionalTruncationMessage; - NSAttributedString *_composedTruncationText; - NSArray *_pointSizeScaleFactors; - NSLineBreakMode _truncationMode; - - NSUInteger _maximumNumberOfLines; - - NSString *_highlightedLinkAttributeName; - id _highlightedLinkAttributeValue; - ASTextNodeHighlightStyle _highlightStyle; - NSRange _highlightRange; - ASHighlightOverlayLayer *_activeHighlightLayer; - - UILongPressGestureRecognizer *_longPressGestureRecognizer; -} -@dynamic placeholderEnabled; - -static NSArray *DefaultLinkAttributeNames() { - static NSArray *names; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - names = @[ NSLinkAttributeName ]; - }); - return names; -} - -- (instancetype)init -{ - if (self = [super init]) { - // Load default values from superclass. - _shadowOffset = [super shadowOffset]; - _shadowColor = CGColorRetain([super shadowColor]); - _shadowOpacity = [super shadowOpacity]; - _shadowRadius = [super shadowRadius]; - - // Disable user interaction for text node by default. - self.userInteractionEnabled = NO; - self.needsDisplayOnBoundsChange = YES; - - _truncationMode = NSLineBreakByWordWrapping; - - // The common case is for a text node to be non-opaque and blended over some background. - self.opaque = NO; - self.backgroundColor = [UIColor clearColor]; - - self.linkAttributeNames = DefaultLinkAttributeNames(); - - // Accessibility - self.isAccessibilityElement = YES; - self.accessibilityTraits = self.defaultAccessibilityTraits; - - // Placeholders - // Disabled by default in ASDisplayNode, but add a few options for those who toggle - // on the special placeholder behavior of ASTextNode. - _placeholderColor = ASDisplayNodeDefaultPlaceholderColor(); - _placeholderInsets = UIEdgeInsetsMake(1.0, 0.0, 1.0, 0.0); - } - - return self; -} - -- (void)dealloc -{ - CGColorRelease(_shadowColor); - - // TODO: This may not be necessary post-iOS-9 when most UIKit assign APIs - // were changed to weak. - if (_longPressGestureRecognizer) { - _longPressGestureRecognizer.delegate = nil; - [_longPressGestureRecognizer removeTarget:nil action:NULL]; - [self.view removeGestureRecognizer:_longPressGestureRecognizer]; - } -} - -#pragma mark - Description - -- (NSString *)_plainStringForDescription -{ - NSString *plainString = [[self.attributedText string] stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]; - if (plainString.length > 50) { - plainString = [[plainString substringToIndex:50] stringByAppendingString:@"\u2026"]; - } - return plainString; -} - -- (NSMutableArray *)propertiesForDescription -{ - NSMutableArray *result = [super propertiesForDescription]; - NSString *plainString = [self _plainStringForDescription]; - if (plainString.length > 0) { - [result addObject:@{ (id)kCFNull : ASStringWithQuotesIfMultiword(plainString) }]; - } - return result; -} - -- (NSMutableArray *)propertiesForDebugDescription -{ - NSMutableArray *result = [super propertiesForDebugDescription]; - NSString *plainString = [self _plainStringForDescription]; - if (plainString.length > 0) { - [result insertObject:@{ @"text" : ASStringWithQuotesIfMultiword(plainString) } atIndex:0]; - } - return result; -} - -#pragma mark - ASDisplayNode - -- (void)didLoad -{ - [super didLoad]; - - // If we are view-backed and the delegate cares, support the long-press callback. - SEL longPressCallback = @selector(textNode:longPressedLinkAttribute:value:atPoint:textRange:); - if (!self.isLayerBacked && [_delegate respondsToSelector:longPressCallback]) { - _longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(_handleLongPress:)]; - _longPressGestureRecognizer.cancelsTouchesInView = self.longPressCancelsTouches; - _longPressGestureRecognizer.delegate = self; - [self.view addGestureRecognizer:_longPressGestureRecognizer]; - } -} - -- (BOOL)supportsLayerBacking -{ - if (!super.supportsLayerBacking) { - return NO; - } - - // If the text contains any links, return NO. - NSAttributedString *attributedText = self.attributedText; - NSRange range = NSMakeRange(0, attributedText.length); - for (NSString *linkAttributeName in _linkAttributeNames) { - __block BOOL hasLink = NO; - [attributedText enumerateAttribute:linkAttributeName inRange:range options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { - hasLink = (value != nil); - *stop = YES; - }]; - if (hasLink) { - return NO; - } - } - return YES; -} - -#pragma mark - Renderer Management - -- (ASTextKitRenderer *)_renderer -{ - ASLockScopeSelf(); - return [self _locked_renderer]; -} - -- (ASTextKitRenderer *)_rendererWithBounds:(CGRect)bounds -{ - ASLockScopeSelf(); - return [self _locked_rendererWithBounds:bounds]; -} - -- (ASTextKitRenderer *)_locked_renderer -{ - ASAssertLocked(__instanceLock__); - return [self _locked_rendererWithBounds:[self _locked_threadSafeBounds]]; -} - -- (ASTextKitRenderer *)_locked_rendererWithBounds:(CGRect)bounds -{ - ASAssertLocked(__instanceLock__); - bounds = UIEdgeInsetsInsetRect(bounds, _textContainerInset); - return rendererForAttributes([self _locked_rendererAttributes], bounds.size); -} - -- (ASTextKitAttributes)_locked_rendererAttributes -{ - ASAssertLocked(__instanceLock__); - return { - .attributedString = _attributedText, - .truncationAttributedString = [self _locked_composedTruncationText], - .lineBreakMode = _truncationMode, - .maximumNumberOfLines = _maximumNumberOfLines, - .exclusionPaths = _exclusionPaths, - // use the property getter so a subclass can provide these scale factors on demand if desired - .pointSizeScaleFactors = self.pointSizeScaleFactors, - .shadowOffset = _shadowOffset, - .shadowColor = _cachedShadowUIColor, - .shadowOpacity = _shadowOpacity, - .shadowRadius = _shadowRadius - }; -} - -- (NSString *)defaultAccessibilityLabel -{ - ASLockScopeSelf(); - return _attributedText.string; -} - -- (UIAccessibilityTraits)defaultAccessibilityTraits -{ - return UIAccessibilityTraitStaticText; -} - -#pragma mark - Layout and Sizing - -- (void)setTextContainerInset:(UIEdgeInsets)textContainerInset -{ - if (ASLockedSelfCompareAssignCustom(_textContainerInset, textContainerInset, UIEdgeInsetsEqualToEdgeInsets)) { - [self setNeedsLayout]; - } -} - -- (UIEdgeInsets)textContainerInset -{ - return ASLockedSelf(_textContainerInset); -} - -- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize -{ - ASLockScopeSelf(); - - ASDisplayNodeAssert(constrainedSize.width >= 0, @"Constrained width for text (%f) is too narrow", constrainedSize.width); - ASDisplayNodeAssert(constrainedSize.height >= 0, @"Constrained height for text (%f) is too short", constrainedSize.height); - - // Cache the original constrained size for final size calculateion - CGSize originalConstrainedSize = constrainedSize; - - [self setNeedsDisplay]; - - ASTextKitRenderer *renderer = [self _locked_rendererWithBounds:{.size = constrainedSize}]; - CGSize size = renderer.size; - if (_attributedText.length > 0) { - self.style.ascender = [[self class] ascenderWithAttributedString:_attributedText]; - self.style.descender = [[_attributedText attribute:NSFontAttributeName atIndex:_attributedText.length - 1 effectiveRange:NULL] descender]; - if (renderer.currentScaleFactor > 0 && renderer.currentScaleFactor < 1.0) { - // while not perfect, this is a good estimate of what the ascender of the scaled font will be. - self.style.ascender *= renderer.currentScaleFactor; - self.style.descender *= renderer.currentScaleFactor; - } - } - - // Add the constrained size back textContainerInset - size.width += (_textContainerInset.left + _textContainerInset.right); - size.height += (_textContainerInset.top + _textContainerInset.bottom); - - return CGSizeMake(std::fmin(size.width, originalConstrainedSize.width), - std::fmin(size.height, originalConstrainedSize.height)); -} - -#pragma mark - Modifying User Text - -// Returns the ascender of the first character in attributedString by also including the line height if specified in paragraph style. -+ (CGFloat)ascenderWithAttributedString:(NSAttributedString *)attributedString -{ - UIFont *font = [attributedString attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL]; - NSParagraphStyle *paragraphStyle = [attributedString attribute:NSParagraphStyleAttributeName atIndex:0 effectiveRange:NULL]; - if (!paragraphStyle) { - return font.ascender; - } - CGFloat lineHeight = MAX(font.lineHeight, paragraphStyle.minimumLineHeight); - if (paragraphStyle.maximumLineHeight > 0) { - lineHeight = MIN(lineHeight, paragraphStyle.maximumLineHeight); - } - return lineHeight + font.descender; -} - -- (NSAttributedString *)attributedText -{ - ASLockScopeSelf(); - return _attributedText; -} - -- (void)setAttributedText:(NSAttributedString *)attributedText -{ - - if (attributedText == nil) { - attributedText = [[NSAttributedString alloc] initWithString:@"" attributes:nil]; - } - - { - ASLockScopeSelf(); - if (ASObjectIsEqual(attributedText, _attributedText)) { - return; - } - - NSAttributedString *cleanedAttributedString = ASCleanseAttributedStringOfCoreTextAttributes(attributedText); - - // Invalidating the truncation text must be done while we still hold the lock. Because after we release it, - // another thread may set a new truncation text that will then be cleared by this thread, other may draw - // this soon-to-be-invalidated text. - [self _locked_invalidateTruncationText]; - - NSUInteger length = cleanedAttributedString.length; - if (length > 0) { - // Updating ascender and descender in one transaction while holding the lock. - ASLayoutElementStyle *style = [self _locked_style]; - style.ascender = [[self class] ascenderWithAttributedString:cleanedAttributedString]; - style.descender = [[attributedText attribute:NSFontAttributeName atIndex:cleanedAttributedString.length - 1 effectiveRange:NULL] descender]; - } - - // Update attributed text with cleaned attributed string - _attributedText = cleanedAttributedString; - } - - // Tell the display node superclasses that the cached layout is incorrect now - [self setNeedsLayout]; - - // Force display to create renderer with new size and redisplay with new string - [self setNeedsDisplay]; - - // Accessiblity - const auto currentAttributedText = self.attributedText; // Grab attributed string again in case it changed in the meantime - self.accessibilityLabel = self.defaultAccessibilityLabel; - self.isAccessibilityElement = (currentAttributedText.length != 0); // We're an accessibility element by default if there is a string. - -#if AS_TEXTNODE_RECORD_ATTRIBUTED_STRINGS - [ASTextNode _registerAttributedText:_attributedText]; -#endif -} - -#pragma mark - Text Layout - -- (void)setExclusionPaths:(NSArray *)exclusionPaths -{ - if (ASLockedSelfCompareAssignCopy(_exclusionPaths, exclusionPaths)) { - [self setNeedsLayout]; - [self setNeedsDisplay]; - } -} - -- (NSArray *)exclusionPaths -{ - return ASLockedSelf(_exclusionPaths); -} - -#pragma mark - Drawing - -- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer -{ - ASLockScopeSelf(); - - return [[ASTextNodeDrawParameter alloc] initWithRendererAttributes:[self _locked_rendererAttributes] - backgroundColor:self.backgroundColor - textContainerInsets:_textContainerInset - contentScale:_contentsScaleForDisplay - opaque:self.isOpaque - bounds:[self threadSafeBounds]]; -} - -+ (UIImage *)displayWithParameters:(id)parameters isCancelled:(NS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelled -{ - ASTextNodeDrawParameter *drawParameter = (ASTextNodeDrawParameter *)parameters; - - if (drawParameter->_bounds.size.width <= 0 || drawParameter->_bounds.size.height <= 0) { - return nil; - } - - UIImage *result = nil; - UIColor *backgroundColor = drawParameter->_backgroundColor; - UIEdgeInsets textContainerInsets = drawParameter ? drawParameter->_textContainerInsets : UIEdgeInsetsZero; - ASTextKitRenderer *renderer = [drawParameter rendererForBounds:drawParameter->_bounds]; - BOOL renderedWithGraphicsRenderer = NO; - - if (AS_AVAILABLE_IOS_TVOS(10, 10)) { - if (ASActivateExperimentalFeature(ASExperimentalTextDrawing)) { - renderedWithGraphicsRenderer = YES; - UIGraphicsImageRenderer *graphicsRenderer = [[UIGraphicsImageRenderer alloc] initWithSize:CGSizeMake(drawParameter->_bounds.size.width, drawParameter->_bounds.size.height)]; - result = [graphicsRenderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) { - CGContextRef context = rendererContext.CGContext; - ASDisplayNodeAssert(context, @"This is no good without a context."); - - CGContextSaveGState(context); - CGContextTranslateCTM(context, textContainerInsets.left, textContainerInsets.top); - - // Fill background - if (backgroundColor != nil) { - [backgroundColor setFill]; - UIRectFillUsingBlendMode(CGContextGetClipBoundingBox(context), kCGBlendModeCopy); - } - - // Draw text - [renderer drawInContext:context bounds:drawParameter->_bounds]; - CGContextRestoreGState(context); - }]; - } - } - - if (!renderedWithGraphicsRenderer) { - UIGraphicsBeginImageContextWithOptions(CGSizeMake(drawParameter->_bounds.size.width, drawParameter->_bounds.size.height), drawParameter->_opaque, drawParameter->_contentScale); - - CGContextRef context = UIGraphicsGetCurrentContext(); - ASDisplayNodeAssert(context, @"This is no good without a context."); - - CGContextSaveGState(context); - CGContextTranslateCTM(context, textContainerInsets.left, textContainerInsets.top); - - // Fill background - if (backgroundColor != nil) { - [backgroundColor setFill]; - UIRectFillUsingBlendMode(CGContextGetClipBoundingBox(context), kCGBlendModeCopy); - } - - // Draw text - [renderer drawInContext:context bounds:drawParameter->_bounds]; - CGContextRestoreGState(context); - - result = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - } - - return result; -} - -#pragma mark - Attributes - -- (id)linkAttributeValueAtPoint:(CGPoint)point - attributeName:(out NSString **)attributeNameOut - range:(out NSRange *)rangeOut -{ - return [self _linkAttributeValueAtPoint:point - attributeName:attributeNameOut - range:rangeOut - inAdditionalTruncationMessage:NULL - forHighlighting:NO]; -} - -- (id)_linkAttributeValueAtPoint:(CGPoint)point - attributeName:(out NSString * __autoreleasing *)attributeNameOut - range:(out NSRange *)rangeOut - inAdditionalTruncationMessage:(out BOOL *)inAdditionalTruncationMessageOut - forHighlighting:(BOOL)highlighting -{ - ASDisplayNodeAssertMainThread(); - - ASLockScopeSelf(); - - ASTextKitRenderer *renderer = [self _locked_renderer]; - NSRange visibleRange = renderer.firstVisibleRange; - NSAttributedString *attributedString = _attributedText; - NSRange clampedRange = NSIntersectionRange(visibleRange, NSMakeRange(0, attributedString.length)); - - // Check in a 9-point region around the actual touch point so we make sure - // we get the best attribute for the touch. - __block CGFloat minimumGlyphDistance = CGFLOAT_MAX; - - // Final output vars - __block id linkAttributeValue = nil; - __block BOOL inTruncationMessage = NO; - - [renderer enumerateTextIndexesAtPosition:point usingBlock:^(NSUInteger characterIndex, CGRect glyphBoundingRect, BOOL *stop) { - CGPoint glyphLocation = CGPointMake(CGRectGetMidX(glyphBoundingRect), CGRectGetMidY(glyphBoundingRect)); - CGFloat currentDistance = std::sqrt(std::pow(point.x - glyphLocation.x, 2.f) + std::pow(point.y - glyphLocation.y, 2.f)); - if (currentDistance >= minimumGlyphDistance) { - // If the distance computed from the touch to the glyph location is - // not the minimum among the located link attributes, we can just skip - // to the next location. - return; - } - - // Check if it's outside the visible range, if so, then we mark this touch - // as inside the truncation message, because in at least one of the touch - // points it was. - if (!(NSLocationInRange(characterIndex, visibleRange))) { - inTruncationMessage = YES; - } - - if (inAdditionalTruncationMessageOut != NULL) { - *inAdditionalTruncationMessageOut = inTruncationMessage; - } - - // Short circuit here if it's just in the truncation message. Since the - // truncation message may be beyond the scope of the actual input string, - // we have to make sure that we don't start asking for attributes on it. - if (inTruncationMessage) { - return; - } - - for (NSString *attributeName in self->_linkAttributeNames) { - NSRange range; - id value = [attributedString attribute:attributeName atIndex:characterIndex longestEffectiveRange:&range inRange:clampedRange]; - NSString *name = attributeName; - - if (value == nil || name == nil) { - // Didn't find anything - continue; - } - - // If highlighting, check with delegate first. If not implemented, assume YES. - if (highlighting - && [self->_delegate respondsToSelector:@selector(textNode:shouldHighlightLinkAttribute:value:atPoint:)] - && ![self->_delegate textNode:self shouldHighlightLinkAttribute:name value:value atPoint:point]) { - value = nil; - name = nil; - } - - if (value != nil || name != nil) { - // We found a minimum glyph distance link attribute, so set the min - // distance, and the out params. - minimumGlyphDistance = currentDistance; - - if (rangeOut != NULL && value != nil) { - *rangeOut = range; - // Limit to only the visible range, because the attributed string will - // return values outside the visible range. - if (NSMaxRange(*rangeOut) > NSMaxRange(visibleRange)) { - (*rangeOut).length = MAX(NSMaxRange(visibleRange) - (*rangeOut).location, 0); - } - } - - if (attributeNameOut != NULL) { - *attributeNameOut = name; - } - - // Set the values for the next iteration - linkAttributeValue = value; - - break; - } - } - }]; - - return linkAttributeValue; -} - -#pragma mark - UIGestureRecognizerDelegate - -- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer -{ - ASDisplayNodeAssertMainThread(); - - if (gestureRecognizer == _longPressGestureRecognizer) { - // Don't allow long press on truncation message - if ([self _pendingTruncationTap]) { - return NO; - } - - // Ask our delegate if a long-press on an attribute is relevant - if ([self _pendingLinkTap] && [_delegate respondsToSelector:@selector(textNode:shouldLongPressLinkAttribute:value:atPoint:)]) { - return [_delegate textNode:self - shouldLongPressLinkAttribute:_highlightedLinkAttributeName - value:_highlightedLinkAttributeValue - atPoint:[gestureRecognizer locationInView:self.view]]; - } - - // Otherwise we are good to go. - return YES; - } - - if (([self _pendingLinkTap] || [self _pendingTruncationTap]) - && [gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]] - && CGRectContainsPoint(self.threadSafeBounds, [gestureRecognizer locationInView:self.view])) { - return NO; - } - - return [super gestureRecognizerShouldBegin:gestureRecognizer]; -} - -#pragma mark - Highlighting - -- (ASTextNodeHighlightStyle)highlightStyle -{ - ASLockScopeSelf(); - - return _highlightStyle; -} - -- (void)setHighlightStyle:(ASTextNodeHighlightStyle)highlightStyle -{ - ASLockScopeSelf(); - - _highlightStyle = highlightStyle; -} - -- (NSRange)highlightRange -{ - ASDisplayNodeAssertMainThread(); - - return _highlightRange; -} - -- (void)setHighlightRange:(NSRange)highlightRange -{ - [self setHighlightRange:highlightRange animated:NO]; -} - -- (void)setHighlightRange:(NSRange)highlightRange animated:(BOOL)animated -{ - [self _setHighlightRange:highlightRange forAttributeName:nil value:nil animated:animated]; -} - -- (void)_setHighlightRange:(NSRange)highlightRange forAttributeName:(NSString *)highlightedAttributeName value:(id)highlightedAttributeValue animated:(BOOL)animated -{ - ASDisplayNodeAssertMainThread(); - - _highlightedLinkAttributeName = highlightedAttributeName; - _highlightedLinkAttributeValue = highlightedAttributeValue; - - if (!NSEqualRanges(highlightRange, _highlightRange) && ((0 != highlightRange.length) || (0 != _highlightRange.length))) { - - _highlightRange = highlightRange; - - if (_activeHighlightLayer) { - if (animated) { - __weak CALayer *weakHighlightLayer = _activeHighlightLayer; - _activeHighlightLayer = nil; - - weakHighlightLayer.opacity = 0.0; - - CFTimeInterval beginTime = CACurrentMediaTime(); - CABasicAnimation *possibleFadeIn = (CABasicAnimation *)[weakHighlightLayer animationForKey:@"opacity"]; - if (possibleFadeIn) { - // Calculate when we should begin fading out based on the end of the fade in animation, - // Also check to make sure that the new begin time hasn't already passed - CGFloat newBeginTime = (possibleFadeIn.beginTime + possibleFadeIn.duration); - if (newBeginTime > beginTime) { - beginTime = newBeginTime; - } - } - - CABasicAnimation *fadeOut = [CABasicAnimation animationWithKeyPath:@"opacity"]; - fadeOut.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; - fadeOut.fromValue = possibleFadeIn.toValue ? : @(((CALayer *)weakHighlightLayer.presentationLayer).opacity); - fadeOut.toValue = @0.0; - fadeOut.fillMode = kCAFillModeBoth; - fadeOut.duration = ASTextNodeHighlightFadeOutDuration; - fadeOut.beginTime = beginTime; - - dispatch_block_t prev = [CATransaction completionBlock]; - [CATransaction setCompletionBlock:^{ - [weakHighlightLayer removeFromSuperlayer]; - }]; - - [weakHighlightLayer addAnimation:fadeOut forKey:fadeOut.keyPath]; - - [CATransaction setCompletionBlock:prev]; - - } else { - [_activeHighlightLayer removeFromSuperlayer]; - _activeHighlightLayer = nil; - } - } - if (0 != highlightRange.length) { - // Find layer in hierarchy that allows us to draw highlighting on. - CALayer *highlightTargetLayer = self.layer; - while (highlightTargetLayer != nil) { - if (highlightTargetLayer.as_allowsHighlightDrawing) { - break; - } - highlightTargetLayer = highlightTargetLayer.superlayer; - } - - if (highlightTargetLayer != nil) { - ASLockScopeSelf(); - ASTextKitRenderer *renderer = [self _locked_renderer]; - - NSArray *highlightRects = [renderer rectsForTextRange:highlightRange measureOption:ASTextKitRendererMeasureOptionBlock]; - NSMutableArray *converted = [NSMutableArray arrayWithCapacity:highlightRects.count]; - for (NSValue *rectValue in highlightRects) { - UIEdgeInsets shadowPadding = renderer.shadower.shadowPadding; - CGRect rendererRect = ASTextNodeAdjustRenderRectForShadowPadding(rectValue.CGRectValue, shadowPadding); - - // The rects returned from renderer don't have `textContainerInset`, - // as well as they are using the `constrainedSize` for layout, - // so we can simply increase the rect by insets to get the full blown layout. - rendererRect.size.width += _textContainerInset.left + _textContainerInset.right; - rendererRect.size.height += _textContainerInset.top + _textContainerInset.bottom; - - CGRect highlightedRect = [self.layer convertRect:rendererRect toLayer:highlightTargetLayer]; - - // We set our overlay layer's frame to the bounds of the highlight target layer. - // Offset highlight rects to avoid double-counting target layer's bounds.origin. - highlightedRect.origin.x -= highlightTargetLayer.bounds.origin.x; - highlightedRect.origin.y -= highlightTargetLayer.bounds.origin.y; - [converted addObject:[NSValue valueWithCGRect:highlightedRect]]; - } - - ASHighlightOverlayLayer *overlayLayer = [[ASHighlightOverlayLayer alloc] initWithRects:converted]; - overlayLayer.highlightColor = [[self class] _highlightColorForStyle:self.highlightStyle]; - overlayLayer.frame = highlightTargetLayer.bounds; - overlayLayer.masksToBounds = NO; - overlayLayer.opacity = [[self class] _highlightOpacityForStyle:self.highlightStyle]; - [highlightTargetLayer addSublayer:overlayLayer]; - - if (animated) { - CABasicAnimation *fadeIn = [CABasicAnimation animationWithKeyPath:@"opacity"]; - fadeIn.fromValue = @0.0; - fadeIn.toValue = @(overlayLayer.opacity); - fadeIn.duration = ASTextNodeHighlightFadeInDuration; - fadeIn.beginTime = CACurrentMediaTime(); - - [overlayLayer addAnimation:fadeIn forKey:fadeIn.keyPath]; - } - - [overlayLayer setNeedsDisplay]; - - _activeHighlightLayer = overlayLayer; - } - } - } -} - -- (void)_clearHighlightIfNecessary -{ - ASDisplayNodeAssertMainThread(); - - if ([self _pendingLinkTap] || [self _pendingTruncationTap]) { - [self setHighlightRange:NSMakeRange(0, 0) animated:YES]; - } -} - -+ (CGColorRef)_highlightColorForStyle:(ASTextNodeHighlightStyle)style -{ - return [UIColor colorWithWhite:(style == ASTextNodeHighlightStyleLight ? 0.0 : 1.0) alpha:1.0].CGColor; -} - -+ (CGFloat)_highlightOpacityForStyle:(ASTextNodeHighlightStyle)style -{ - return (style == ASTextNodeHighlightStyleLight) ? ASTextNodeHighlightLightOpacity : ASTextNodeHighlightDarkOpacity; -} - -#pragma mark - Text rects - -static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UIEdgeInsets shadowPadding) { - rendererRect.origin.x -= shadowPadding.left; - rendererRect.origin.y -= shadowPadding.top; - return rendererRect; -} - -- (NSArray *)rectsForTextRange:(NSRange)textRange -{ - return [self _rectsForTextRange:textRange measureOption:ASTextKitRendererMeasureOptionCapHeight]; -} - -- (NSArray *)highlightRectsForTextRange:(NSRange)textRange -{ - return [self _rectsForTextRange:textRange measureOption:ASTextKitRendererMeasureOptionBlock]; -} - -- (NSArray *)_rectsForTextRange:(NSRange)textRange measureOption:(ASTextKitRendererMeasureOption)measureOption -{ - ASLockScopeSelf(); - - NSArray *rects = [[self _locked_renderer] rectsForTextRange:textRange measureOption:measureOption]; - const auto adjustedRects = [[NSMutableArray alloc] init]; - - for (NSValue *rectValue in rects) { - CGRect rect = [rectValue CGRectValue]; - rect = ASTextNodeAdjustRenderRectForShadowPadding(rect, self.shadowPadding); - - NSValue *adjustedRectValue = [NSValue valueWithCGRect:rect]; - [adjustedRects addObject:adjustedRectValue]; - } - - return adjustedRects; -} - -- (CGRect)trailingRect -{ - ASLockScopeSelf(); - - CGRect rect = [[self _locked_renderer] trailingRect]; - return ASTextNodeAdjustRenderRectForShadowPadding(rect, self.shadowPadding); -} - -- (CGRect)frameForTextRange:(NSRange)textRange -{ - ASLockScopeSelf(); - - CGRect frame = [[self _locked_renderer] frameForTextRange:textRange]; - return ASTextNodeAdjustRenderRectForShadowPadding(frame, self.shadowPadding); -} - -#pragma mark - Placeholders - -- (UIColor *)placeholderColor -{ - return ASLockedSelf(_placeholderColor); -} - -- (void)setPlaceholderColor:(UIColor *)placeholderColor -{ - if (ASLockedSelfCompareAssignCopy(_placeholderColor, placeholderColor)) { - self.placeholderEnabled = CGColorGetAlpha(placeholderColor.CGColor) > 0; - } -} - -- (UIImage *)placeholderImage -{ - // FIXME: Replace this implementation with reusable CALayers that have .backgroundColor set. - // This would completely eliminate the memory and performance cost of the backing store. - CGSize size = self.calculatedSize; - if ((size.width * size.height) < CGFLOAT_EPSILON) { - return nil; - } - - ASLockScopeSelf(); - - ASGraphicsBeginImageContextWithOptions(size, NO, 1.0); - [self.placeholderColor setFill]; - - ASTextKitRenderer *renderer = [self _locked_renderer]; - NSRange visibleRange = renderer.firstVisibleRange; - - // cap height is both faster and creates less subpixel blending - NSArray *lineRects = [self _rectsForTextRange:visibleRange measureOption:ASTextKitRendererMeasureOptionLineHeight]; - - // fill each line with the placeholder color - for (NSValue *rectValue in lineRects) { - CGRect lineRect = [rectValue CGRectValue]; - CGRect fillBounds = CGRectIntegral(UIEdgeInsetsInsetRect(lineRect, self.placeholderInsets)); - - if (fillBounds.size.width > 0.0 && fillBounds.size.height > 0.0) { - UIRectFill(fillBounds); - } - } - - UIImage *image = ASGraphicsGetImageAndEndCurrentContext(); - return image; -} - -#pragma mark - Touch Handling - -- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - - if (!_passthroughNonlinkTouches) { - return [super pointInside:point withEvent:event]; - } - - NSRange range = NSMakeRange(0, 0); - NSString *linkAttributeName = nil; - BOOL inAdditionalTruncationMessage = NO; - - id linkAttributeValue = [self _linkAttributeValueAtPoint:point - attributeName:&linkAttributeName - range:&range - inAdditionalTruncationMessage:&inAdditionalTruncationMessage - forHighlighting:YES]; - - NSUInteger lastCharIndex = NSIntegerMax; - BOOL linkCrossesVisibleRange = (lastCharIndex > range.location) && (lastCharIndex < NSMaxRange(range) - 1); - - if (range.length > 0 && !linkCrossesVisibleRange && linkAttributeValue != nil && linkAttributeName != nil) { - return YES; - } else { - return NO; - } -} - -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - - [super touchesBegan:touches withEvent:event]; - - CGPoint point = [[touches anyObject] locationInView:self.view]; - - NSRange range = NSMakeRange(0, 0); - NSString *linkAttributeName = nil; - BOOL inAdditionalTruncationMessage = NO; - - id linkAttributeValue = [self _linkAttributeValueAtPoint:point - attributeName:&linkAttributeName - range:&range - inAdditionalTruncationMessage:&inAdditionalTruncationMessage - forHighlighting:YES]; - - NSUInteger lastCharIndex = NSIntegerMax; - BOOL linkCrossesVisibleRange = (lastCharIndex > range.location) && (lastCharIndex < NSMaxRange(range) - 1); - - if (inAdditionalTruncationMessage) { - NSRange visibleRange = NSMakeRange(0, 0); - { - ASLockScopeSelf(); - visibleRange = [self _locked_renderer].firstVisibleRange; - } - NSRange truncationMessageRange = [self _additionalTruncationMessageRangeWithVisibleRange:visibleRange]; - [self _setHighlightRange:truncationMessageRange forAttributeName:ASTextNodeTruncationTokenAttributeName value:nil animated:YES]; - } else if (range.length > 0 && !linkCrossesVisibleRange && linkAttributeValue != nil && linkAttributeName != nil) { - [self _setHighlightRange:range forAttributeName:linkAttributeName value:linkAttributeValue animated:YES]; - } -} - - -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - [super touchesCancelled:touches withEvent:event]; - - [self _clearHighlightIfNecessary]; -} - -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - [super touchesEnded:touches withEvent:event]; - - if ([self _pendingLinkTap] && [_delegate respondsToSelector:@selector(textNode:tappedLinkAttribute:value:atPoint:textRange:)]) { - CGPoint point = [[touches anyObject] locationInView:self.view]; - [_delegate textNode:self tappedLinkAttribute:_highlightedLinkAttributeName value:_highlightedLinkAttributeValue atPoint:point textRange:_highlightRange]; - } - - if ([self _pendingTruncationTap]) { - if ([_delegate respondsToSelector:@selector(textNodeTappedTruncationToken:)]) { - [_delegate textNodeTappedTruncationToken:self]; - } - } - - [self _clearHighlightIfNecessary]; -} - -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - [super touchesMoved:touches withEvent:event]; - - UITouch *touch = [touches anyObject]; - CGPoint locationInView = [touch locationInView:self.view]; - // on 3D Touch enabled phones, this gets fired with changes in force, and usually will get fired immediately after touchesBegan:withEvent: - if (CGPointEqualToPoint([touch previousLocationInView:self.view], locationInView)) - return; - - // If touch has moved out of the current highlight range, clear the highlight. - if (_highlightRange.length > 0) { - NSRange range = NSMakeRange(0, 0); - [self _linkAttributeValueAtPoint:locationInView - attributeName:NULL - range:&range - inAdditionalTruncationMessage:NULL - forHighlighting:YES]; - - if (!NSEqualRanges(_highlightRange, range)) { - [self _clearHighlightIfNecessary]; - } - } -} - -- (void)_handleLongPress:(UILongPressGestureRecognizer *)longPressRecognizer -{ - ASDisplayNodeAssertMainThread(); - - // Respond to long-press when it begins, not when it ends. - if (longPressRecognizer.state == UIGestureRecognizerStateBegan) { - if ([self _pendingLinkTap] && [_delegate respondsToSelector:@selector(textNode:longPressedLinkAttribute:value:atPoint:textRange:)]) { - CGPoint touchPoint = [_longPressGestureRecognizer locationInView:self.view]; - [_delegate textNode:self longPressedLinkAttribute:_highlightedLinkAttributeName value:_highlightedLinkAttributeValue atPoint:touchPoint textRange:_highlightRange]; - } - } -} - -- (BOOL)_pendingLinkTap -{ - ASLockScopeSelf(); - - return (_highlightedLinkAttributeValue != nil && ![self _pendingTruncationTap]) && _delegate != nil; -} - -- (BOOL)_pendingTruncationTap -{ - ASLockScopeSelf(); - - return [_highlightedLinkAttributeName isEqualToString:ASTextNodeTruncationTokenAttributeName]; -} - -#pragma mark - Shadow Properties - -- (CGColorRef)shadowColor -{ - return ASLockedSelf(_shadowColor); -} - -- (void)setShadowColor:(CGColorRef)shadowColor -{ - [self lock]; - - if (_shadowColor != shadowColor && CGColorEqualToColor(shadowColor, _shadowColor) == NO) { - CGColorRelease(_shadowColor); - _shadowColor = CGColorRetain(shadowColor); - _cachedShadowUIColor = [UIColor colorWithCGColor:shadowColor]; - [self unlock]; - - [self setNeedsDisplay]; - return; - } - - [self unlock]; -} - -- (CGSize)shadowOffset -{ - return ASLockedSelf(_shadowOffset); -} - -- (void)setShadowOffset:(CGSize)shadowOffset -{ - if (ASLockedSelfCompareAssignCustom(_shadowOffset, shadowOffset, CGSizeEqualToSize)) { - [self setNeedsDisplay]; - } -} - -- (CGFloat)shadowOpacity -{ - return ASLockedSelf(_shadowOpacity); -} - -- (void)setShadowOpacity:(CGFloat)shadowOpacity -{ - if (ASLockedSelfCompareAssign(_shadowOpacity, shadowOpacity)) { - [self setNeedsDisplay]; - } -} - -- (CGFloat)shadowRadius -{ - return ASLockedSelf(_shadowRadius); -} - -- (void)setShadowRadius:(CGFloat)shadowRadius -{ - if (ASLockedSelfCompareAssign(_shadowRadius, shadowRadius)) { - [self setNeedsDisplay]; - } -} - -- (UIEdgeInsets)shadowPadding -{ - ASLockScopeSelf(); - return [self _locked_renderer].shadower.shadowPadding; -} - -#pragma mark - Truncation Message - -static NSAttributedString *DefaultTruncationAttributedString() -{ - static NSAttributedString *defaultTruncationAttributedString; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - defaultTruncationAttributedString = [[NSAttributedString alloc] initWithString:NSLocalizedString(@"\u2026", @"Default truncation string")]; - }); - return defaultTruncationAttributedString; -} - -- (NSAttributedString *)truncationAttributedText -{ - return ASLockedSelf(_truncationAttributedText); -} - -- (void)setTruncationAttributedText:(NSAttributedString *)truncationAttributedText -{ - if (ASLockedSelfCompareAssignCopy(_truncationAttributedText, truncationAttributedText)) { - [self _invalidateTruncationText]; - [self setNeedsDisplay]; - } -} - -- (void)setAdditionalTruncationMessage:(NSAttributedString *)additionalTruncationMessage -{ - if (ASLockedSelfCompareAssignCopy(_additionalTruncationMessage, additionalTruncationMessage)) { - [self _invalidateTruncationText]; - [self setNeedsDisplay]; - } -} - -- (NSAttributedString *)additionalTruncationMessage -{ - return ASLockedSelf(_additionalTruncationMessage); -} - -- (void)setTruncationMode:(NSLineBreakMode)truncationMode -{ - if (ASLockedSelfCompareAssign(_truncationMode, truncationMode)) { - [self setNeedsDisplay]; - } -} - -- (NSLineBreakMode)truncationMode -{ - return ASLockedSelf(_truncationMode); -} - -- (BOOL)isTruncated -{ - return ASLockedSelf([[self _locked_renderer] isTruncated]); -} - -- (BOOL)shouldTruncateForConstrainedSize:(ASSizeRange)constrainedSize -{ - return ASLockedSelf([[self _locked_rendererWithBounds:{.size = constrainedSize.max}] isTruncated]); -} - -- (void)setPointSizeScaleFactors:(NSArray *)pointSizeScaleFactors -{ - if (ASLockedSelfCompareAssignCopy(_pointSizeScaleFactors, pointSizeScaleFactors)) { - [self setNeedsDisplay]; - } -} - -- (NSArray *)pointSizeScaleFactors -{ - return ASLockedSelf(_pointSizeScaleFactors); -} - -- (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines -{ - if (ASLockedSelfCompareAssign(_maximumNumberOfLines, maximumNumberOfLines)) { - [self setNeedsDisplay]; - } -} - -- (NSUInteger)maximumNumberOfLines -{ - return ASLockedSelf(_maximumNumberOfLines); -} - -- (NSUInteger)lineCount -{ - return ASLockedSelf([[self _locked_renderer] lineCount]); -} - -#pragma mark - Truncation Message - -- (void)_invalidateTruncationText -{ - ASLockScopeSelf(); - [self _locked_invalidateTruncationText]; -} - -- (void)_locked_invalidateTruncationText -{ - _composedTruncationText = nil; -} - -/** - * @return the additional truncation message range within the as-rendered text. - * Must be called from main thread - */ -- (NSRange)_additionalTruncationMessageRangeWithVisibleRange:(NSRange)visibleRange -{ - ASLockScopeSelf(); - - // Check if we even have an additional truncation message. - if (!_additionalTruncationMessage) { - return NSMakeRange(NSNotFound, 0); - } - - // Character location of the unicode ellipsis (the first index after the visible range) - NSInteger truncationTokenIndex = NSMaxRange(visibleRange); - - NSUInteger additionalTruncationMessageLength = _additionalTruncationMessage.length; - // We get the location of the truncation token, then add the length of the - // truncation attributed string +1 for the space between. - return NSMakeRange(truncationTokenIndex + _truncationAttributedText.length + 1, additionalTruncationMessageLength); -} - -/** - * @return the truncation message for the string. If there are both an - * additional truncation message and a truncation attributed string, they will - * be properly composed. - */ -- (NSAttributedString *)_locked_composedTruncationText -{ - ASAssertLocked(__instanceLock__); - if (_composedTruncationText == nil) { - if (_truncationAttributedText != nil && _additionalTruncationMessage != nil) { - NSMutableAttributedString *newComposedTruncationString = [[NSMutableAttributedString alloc] initWithAttributedString:_truncationAttributedText]; - [newComposedTruncationString.mutableString appendString:@" "]; - [newComposedTruncationString appendAttributedString:_additionalTruncationMessage]; - _composedTruncationText = newComposedTruncationString; - } else if (_truncationAttributedText != nil) { - _composedTruncationText = _truncationAttributedText; - } else if (_additionalTruncationMessage != nil) { - _composedTruncationText = _additionalTruncationMessage; - } else { - _composedTruncationText = DefaultTruncationAttributedString(); - } - _composedTruncationText = [self _locked_prepareTruncationStringForDrawing:_composedTruncationText]; - } - return _composedTruncationText; -} - -/** - * - cleanses it of core text attributes so TextKit doesn't crash - * - Adds whole-string attributes so the truncation message matches the styling - * of the body text - */ -- (NSAttributedString *)_locked_prepareTruncationStringForDrawing:(NSAttributedString *)truncationString -{ - ASAssertLocked(__instanceLock__); - truncationString = ASCleanseAttributedStringOfCoreTextAttributes(truncationString); - NSMutableAttributedString *truncationMutableString = [truncationString mutableCopy]; - // Grab the attributes from the full string - if (_attributedText.length > 0) { - NSAttributedString *originalString = _attributedText; - NSInteger originalStringLength = _attributedText.length; - // Add any of the original string's attributes to the truncation string, - // but don't overwrite any of the truncation string's attributes - NSDictionary *originalStringAttributes = [originalString attributesAtIndex:originalStringLength-1 effectiveRange:NULL]; - [truncationString enumerateAttributesInRange:NSMakeRange(0, truncationString.length) options:0 usingBlock: - ^(NSDictionary *attributes, NSRange range, BOOL *stop) { - NSMutableDictionary *futureTruncationAttributes = [originalStringAttributes mutableCopy]; - [futureTruncationAttributes addEntriesFromDictionary:attributes]; - [truncationMutableString setAttributes:futureTruncationAttributes range:range]; - }]; - } - return truncationMutableString; -} - -#if AS_TEXTNODE_RECORD_ATTRIBUTED_STRINGS -+ (void)_registerAttributedText:(NSAttributedString *)str -{ - static NSMutableArray *array; - static NSLock *lock; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - lock = [NSLock new]; - array = [NSMutableArray new]; - }); - [lock lock]; - [array addObject:str]; - if (array.count % 20 == 0) { - NSLog(@"Got %d strings", (int)array.count); - } - if (array.count == 2000) { - NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"AttributedStrings.plist"]; - NSAssert([NSKeyedArchiver archiveRootObject:array toFile:path], nil); - NSLog(@"Saved to %@", path); - } - [lock unlock]; -} -#endif - -// All direct descendants of ASTextNode get their superclass replaced by ASTextNode2. -+ (void)initialize -{ - // Texture requires that node subclasses call [super initialize] - [super initialize]; - - if (class_getSuperclass(self) == [ASTextNode class] - && ASActivateExperimentalFeature(ASExperimentalTextNode)) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - class_setSuperclass(self, [ASTextNode2 class]); -#pragma clang diagnostic pop - } -} - -// For direct allocations of ASTextNode itself, we override allocWithZone: -+ (id)allocWithZone:(struct _NSZone *)zone -{ - if (ASActivateExperimentalFeature(ASExperimentalTextNode)) { - return (ASTextNode *)[ASTextNode2 allocWithZone:zone]; - } else { - return [super allocWithZone:zone]; - } -} - -@end - -@implementation ASTextNode (Unsupported) - -- (void)setTextContainerLinePositionModifier:(id)textContainerLinePositionModifier -{ - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); -} - -- (id)textContainerLinePositionModifier -{ - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - return nil; -} - -@end - -@implementation ASTextNode (Deprecated) - -- (void)setAttributedString:(NSAttributedString *)attributedString -{ - self.attributedText = attributedString; -} - -- (NSAttributedString *)attributedString -{ - return self.attributedText; -} - -- (void)setTruncationAttributedString:(NSAttributedString *)truncationAttributedString -{ - self.truncationAttributedText = truncationAttributedString; -} - -- (NSAttributedString *)truncationAttributedString -{ - return self.truncationAttributedText; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASTextNode2.h b/submodules/AsyncDisplayKit/Source/ASTextNode2.h deleted file mode 100644 index 254ce40a7e..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTextNode2.h +++ /dev/null @@ -1,242 +0,0 @@ -// -// ASTextNode2.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - -@protocol ASTextLinePositionModifier; - -NS_ASSUME_NONNULL_BEGIN - -/** - @abstract Draws interactive rich text. - @discussion Backed by the code in TextExperiment folder, on top of CoreText. - */ -#if AS_ENABLE_TEXTNODE -@interface ASTextNode2 : ASControlNode -#else -@interface ASTextNode : ASControlNode -#endif - -/** - @abstract The styled text displayed by the node. - @discussion Defaults to nil, no text is shown. - For inline image attachments, add an attribute of key NSAttachmentAttributeName, with a value of an NSTextAttachment. - */ -@property (nullable, copy) NSAttributedString *attributedText; - -#pragma mark - Truncation - -/** - @abstract The attributedText to use when the text must be truncated. - @discussion Defaults to a localized ellipsis character. - */ -@property (nullable, copy) NSAttributedString *truncationAttributedText; - -/** - @summary The second attributed string appended for truncation. - @discussion This string will be highlighted on touches. - @default nil - */ -@property (nullable, copy) NSAttributedString *additionalTruncationMessage; - -/** - @abstract Determines how the text is truncated to fit within the receiver's maximum size. - @discussion Defaults to NSLineBreakByWordWrapping. - @note Setting a truncationMode in attributedString will override the truncation mode set here. - */ -@property NSLineBreakMode truncationMode; - -/** - @abstract If the text node is truncated. Text must have been sized first. - */ -@property (readonly, getter=isTruncated) BOOL truncated; - -/** - @abstract The maximum number of lines to render of the text before truncation. - @default 0 (No limit) - */ -@property NSUInteger maximumNumberOfLines; - -/** - @abstract The number of lines in the text. Text must have been sized first. - */ -@property (readonly) NSUInteger lineCount; - -/** - * An array of path objects representing the regions where text should not be displayed. - * - * @discussion The default value of this property is an empty array. You can - * assign an array of UIBezierPath objects to exclude text from one or more regions in - * the text node's bounds. You can use this property to have text wrap around images, - * shapes or other text like a fancy magazine. - */ -@property (nullable, copy) NSArray *exclusionPaths; - -#pragma mark - Placeholders - -/** - * @abstract ASTextNode has a special placeholder behavior when placeholderEnabled is YES. - * - * @discussion Defaults to NO. When YES, it draws rectangles for each line of text, - * following the true shape of the text's wrapping. This visually mirrors the overall - * shape and weight of paragraphs, making the appearance of the finished text less jarring. - */ -@property BOOL placeholderEnabled; - -/** - @abstract The placeholder color. - */ -@property (nullable, copy) UIColor *placeholderColor; - -/** - @abstract Inset each line of the placeholder. - */ -@property UIEdgeInsets placeholderInsets; - -#pragma mark - Shadow - -/** - @abstract When you set these ASDisplayNode properties, they are composited into the bitmap instead of being applied by CA. - - @property (nonatomic) CGColorRef shadowColor; - @property (nonatomic) CGFloat shadowOpacity; - @property (nonatomic) CGSize shadowOffset; - @property (nonatomic) CGFloat shadowRadius; - */ - -/** - @abstract The number of pixels used for shadow padding on each side of the receiver. - @discussion Each inset will be less than or equal to zero, so that applying - UIEdgeInsetsRect(boundingRectForText, shadowPadding) - will return a CGRect large enough to fit both the text and the appropriate shadow padding. - */ -@property (nonatomic, readonly) UIEdgeInsets shadowPadding; - -#pragma mark - Positioning - -/** - @abstract Returns an array of rects bounding the characters in a given text range. - @param textRange A range of text. Must be valid for the receiver's string. - @discussion Use this method to detect all the different rectangles a given range of text occupies. - The rects returned are not guaranteed to be contiguous (for example, if the given text range spans - a line break, the rects returned will be on opposite sides and different lines). The rects returned - are in the coordinate system of the receiver. - */ -- (NSArray *)rectsForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT; - -/** - @abstract Returns an array of rects used for highlighting the characters in a given text range. - @param textRange A range of text. Must be valid for the receiver's string. - @discussion Use this method to detect all the different rectangles the highlights of a given range of text occupies. - The rects returned are not guaranteed to be contiguous (for example, if the given text range spans - a line break, the rects returned will be on opposite sides and different lines). The rects returned - are in the coordinate system of the receiver. This method is useful for visual coordination with a - highlighted range of text. - */ -- (NSArray *)highlightRectsForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT; - -/** - @abstract Returns a bounding rect for the given text range. - @param textRange A range of text. Must be valid for the receiver's string. - @discussion The height of the frame returned is that of the receiver's line-height; adjustment for - cap-height and descenders is not performed. This method raises an exception if textRange is not - a valid substring range of the receiver's string. - */ -- (CGRect)frameForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT; - -/** - @abstract Returns the trailing rectangle of space in the receiver, after the final character. - @discussion Use this method to detect which portion of the receiver is not occupied by characters. - The rect returned is in the coordinate system of the receiver. - */ -- (CGRect)trailingRect AS_WARN_UNUSED_RESULT; - - -#pragma mark - Actions - -/** - @abstract The set of attribute names to consider links. Defaults to NSLinkAttributeName. - */ -@property (nonatomic, copy) NSArray *linkAttributeNames; - -/** - @abstract Indicates whether the receiver has an entity at a given point. - @param point The point, in the receiver's coordinate system. - @param attributeNameOut The name of the attribute at the point. Can be NULL. - @param rangeOut The ultimate range of the found text. Can be NULL. - @result YES if an entity exists at `point`; NO otherwise. - */ -- (nullable id)linkAttributeValueAtPoint:(CGPoint)point attributeName:(out NSString * _Nullable * _Nullable)attributeNameOut range:(out NSRange * _Nullable)rangeOut AS_WARN_UNUSED_RESULT; - -/** - @abstract The style to use when highlighting text. - */ -@property (nonatomic) ASTextNodeHighlightStyle highlightStyle; - -/** - @abstract The range of text highlighted by the receiver. Changes to this property are not animated by default. - */ -@property (nonatomic) NSRange highlightRange; - -/** - @abstract Set the range of text to highlight, with optional animation. - - @param highlightRange The range of text to highlight. - - @param animated Whether the text should be highlighted with an animation. - */ -- (void)setHighlightRange:(NSRange)highlightRange animated:(BOOL)animated; - -/** - @abstract Responds to actions from links in the text node. - @discussion The delegate must be set before the node is loaded, and implement - textNode:longPressedLinkAttribute:value:atPoint:textRange: in order for - the long press gesture recognizer to be installed. - */ -@property (weak) id delegate; - -/** - @abstract If YES and a long press is recognized, touches are cancelled. Default is NO - */ -@property (nonatomic) BOOL longPressCancelsTouches; - -/** - @abstract if YES will not intercept touches for non-link areas of the text. Default is NO. - */ -@property (nonatomic) BOOL passthroughNonlinkTouches; - -+ (void)enableDebugging; - -#pragma mark - Layout and Sizing - -@property (nullable, nonatomic) id textContainerLinePositionModifier; - -@end - -#if AS_ENABLE_TEXTNODE -@interface ASTextNode2 (Unavailable) -#else -@interface ASTextNode (Unavailable) -#endif - -- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_UNAVAILABLE; - -- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_UNAVAILABLE; - -@end - -#if (!AS_ENABLE_TEXTNODE) -// For the time beeing remap ASTextNode2 to ASTextNode -#define ASTextNode2 ASTextNode -#endif - -NS_ASSUME_NONNULL_END - - diff --git a/submodules/AsyncDisplayKit/Source/ASTextNode2.mm b/submodules/AsyncDisplayKit/Source/ASTextNode2.mm deleted file mode 100644 index 7fa844ba1d..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTextNode2.mm +++ /dev/null @@ -1,1333 +0,0 @@ -// -// ASTextNode2.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import // Definition of ASTextNodeDelegate - -#import -#import - -#import -#import -#import -#import -#import -#import - -#import -#import -#import - -#import - -#import -#import -#import -#import - -@interface ASTextCacheValue : NSObject { - @package - AS::Mutex _m; - std::deque> _layouts; -} -@end -@implementation ASTextCacheValue -@end - -/** - * If set, we will record all values set to attributedText into an array - * and once we get 2000, we'll write them all out into a plist file. - * - * This is useful for gathering realistic text data sets from apps for performance - * testing. - */ -#define AS_TEXTNODE2_RECORD_ATTRIBUTED_STRINGS 0 - -/** - * If it can't find a compatible layout, this method creates one. - * - * NOTE: Be careful to copy `text` if needed. - */ -static NS_RETURNS_RETAINED ASTextLayout *ASTextNodeCompatibleLayoutWithContainerAndText(ASTextContainer *container, NSAttributedString *text) { - static dispatch_once_t onceToken; - static AS::Mutex *layoutCacheLock; - static NSCache *textLayoutCache; - dispatch_once(&onceToken, ^{ - layoutCacheLock = new AS::Mutex(); - textLayoutCache = [[NSCache alloc] init]; - }); - - layoutCacheLock->lock(); - - ASTextCacheValue *cacheValue = [textLayoutCache objectForKey:text]; - if (cacheValue == nil) { - cacheValue = [[ASTextCacheValue alloc] init]; - [textLayoutCache setObject:cacheValue forKey:[text copy]]; - } - - // Lock the cache item for the rest of the method. Only after acquiring can we release the NSCache. - AS::MutexLocker lock(cacheValue->_m); - layoutCacheLock->unlock(); - - CGRect containerBounds = (CGRect){ .size = container.size }; - { - for (const auto &t : cacheValue->_layouts) { - CGSize constrainedSize = std::get<0>(t); - ASTextLayout *layout = std::get<1>(t); - - CGSize layoutSize = layout.textBoundingSize; - // 1. CoreText can return frames that are narrower than the constrained width, for obvious reasons. - // 2. CoreText can return frames that are slightly wider than the constrained width, for some reason. - // We have to trust that somehow it's OK to try and draw within our size constraint, despite the return value. - // 3. Thus, those two values (constrained width & returned width) form a range, where - // intermediate values in that range will be snapped. Thus, we can use a given layout as long as our - // width is in that range, between the min and max of those two values. - CGRect minRect = CGRectMake(0, 0, MIN(layoutSize.width, constrainedSize.width), MIN(layoutSize.height, constrainedSize.height)); - if (!CGRectContainsRect(containerBounds, minRect)) { - continue; - } - CGRect maxRect = CGRectMake(0, 0, MAX(layoutSize.width, constrainedSize.width), MAX(layoutSize.height, constrainedSize.height)); - if (!CGRectContainsRect(maxRect, containerBounds)) { - continue; - } - if (!CGSizeEqualToSize(container.size, constrainedSize)) { - continue; - } - - // Now check container params. - ASTextContainer *otherContainer = layout.container; - if (!UIEdgeInsetsEqualToEdgeInsets(container.insets, otherContainer.insets)) { - continue; - } - if (!ASObjectIsEqual(container.exclusionPaths, otherContainer.exclusionPaths)) { - continue; - } - if (container.maximumNumberOfRows != otherContainer.maximumNumberOfRows) { - continue; - } - if (container.truncationType != otherContainer.truncationType) { - continue; - } - if (!ASObjectIsEqual(container.truncationToken, otherContainer.truncationToken)) { - continue; - } - // TODO: When we get a cache hit, move this entry to the front (LRU). - return layout; - } - } - - // Cache Miss. Compute the text layout. - ASTextLayout *layout = [ASTextLayout layoutWithContainer:container text:text]; - - // Store the result in the cache. - { - // This is a critical section. However we also must hold the lock until this point, in case - // another thread requests this cache item while a layout is being calculated, so they don't race. - cacheValue->_layouts.push_front(std::make_tuple(container.size, layout)); - if (cacheValue->_layouts.size() > 3) { - cacheValue->_layouts.pop_back(); - } - } - - return layout; -} - -static const CGFloat ASTextNodeHighlightLightOpacity = 0.11; -static const CGFloat ASTextNodeHighlightDarkOpacity = 0.22; -static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncationAttribute"; - -#if AS_ENABLE_TEXTNODE -@interface ASTextNode2 () -#else -@interface ASTextNode () -#endif - -@end - -#if AS_ENABLE_TEXTNODE -@implementation ASTextNode2 { -#else -@implementation ASTextNode { -#endif - ASTextContainer *_textContainer; - - CGSize _shadowOffset; - CGColorRef _shadowColor; - CGFloat _shadowOpacity; - CGFloat _shadowRadius; - - NSAttributedString *_attributedText; - NSAttributedString *_truncationAttributedText; - NSAttributedString *_additionalTruncationMessage; - NSAttributedString *_composedTruncationText; - NSArray *_pointSizeScaleFactors; - NSLineBreakMode _truncationMode; - - NSString *_highlightedLinkAttributeName; - id _highlightedLinkAttributeValue; - ASTextNodeHighlightStyle _highlightStyle; - NSRange _highlightRange; - ASHighlightOverlayLayer *_activeHighlightLayer; - UIColor *_placeholderColor; - - UILongPressGestureRecognizer *_longPressGestureRecognizer; -} -@dynamic placeholderEnabled; - -static NSArray *DefaultLinkAttributeNames() { - static NSArray *names; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - names = @[ NSLinkAttributeName ]; - }); - return names; -} - -- (instancetype)init -{ - if (self = [super init]) { - _textContainer = [[ASTextContainer alloc] init]; - // Load default values from superclass. - _shadowOffset = [super shadowOffset]; - _shadowColor = CGColorRetain([super shadowColor]); - _shadowOpacity = [super shadowOpacity]; - _shadowRadius = [super shadowRadius]; - - // Disable user interaction for text node by default. - self.userInteractionEnabled = NO; - self.needsDisplayOnBoundsChange = YES; - - _textContainer.truncationType = ASTextTruncationTypeEnd; - - // The common case is for a text node to be non-opaque and blended over some background. - self.opaque = NO; - self.backgroundColor = [UIColor clearColor]; - - self.linkAttributeNames = DefaultLinkAttributeNames(); - - // Accessibility - self.isAccessibilityElement = YES; - self.accessibilityTraits = self.defaultAccessibilityTraits; - - // Placeholders - // Disabled by default in ASDisplayNode, but add a few options for those who toggle - // on the special placeholder behavior of ASTextNode. - _placeholderColor = ASDisplayNodeDefaultPlaceholderColor(); - _placeholderInsets = UIEdgeInsetsMake(1.0, 0.0, 1.0, 0.0); - } - - return self; -} - -- (void)dealloc -{ - CGColorRelease(_shadowColor); - - if (_longPressGestureRecognizer) { - _longPressGestureRecognizer.delegate = nil; - [_longPressGestureRecognizer removeTarget:nil action:NULL]; - [self.view removeGestureRecognizer:_longPressGestureRecognizer]; - } -} - -#pragma mark - Description - -- (NSString *)_plainStringForDescription -{ - NSString *plainString = [[self.attributedText string] stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]; - if (plainString.length > 50) { - plainString = [[plainString substringToIndex:50] stringByAppendingString:@"…"]; - } - return plainString; -} - -- (NSMutableArray *)propertiesForDescription -{ - NSMutableArray *result = [super propertiesForDescription]; - NSString *plainString = [self _plainStringForDescription]; - if (plainString.length > 0) { - [result insertObject:@{ @"text" : ASStringWithQuotesIfMultiword(plainString) } atIndex:0]; - } - return result; -} - -- (NSMutableArray *)propertiesForDebugDescription -{ - NSMutableArray *result = [super propertiesForDebugDescription]; - NSString *plainString = [self _plainStringForDescription]; - if (plainString.length > 0) { - [result insertObject:@{ @"text" : ASStringWithQuotesIfMultiword(plainString) } atIndex:0]; - } - return result; -} - -#pragma mark - ASDisplayNode - -- (void)didLoad -{ - [super didLoad]; - - // If we are view-backed and the delegate cares, support the long-press callback. - // Locking is not needed, as all instance variables used are main-thread-only. - SEL longPressCallback = @selector(textNode:longPressedLinkAttribute:value:atPoint:textRange:); - if (!self.isLayerBacked && [self.delegate respondsToSelector:longPressCallback]) { - _longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(_handleLongPress:)]; - _longPressGestureRecognizer.cancelsTouchesInView = self.longPressCancelsTouches; - _longPressGestureRecognizer.delegate = self; - [self.view addGestureRecognizer:_longPressGestureRecognizer]; - } -} - -- (BOOL)supportsLayerBacking -{ - if (!super.supportsLayerBacking) { - return NO; - } - - ASLockScopeSelf(); - // If the text contains any links, return NO. - NSAttributedString *attributedText = _attributedText; - NSRange range = NSMakeRange(0, attributedText.length); - for (NSString *linkAttributeName in _linkAttributeNames) { - __block BOOL hasLink = NO; - [attributedText enumerateAttribute:linkAttributeName inRange:range options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { - hasLink = (value != nil); - *stop = YES; - }]; - if (hasLink) { - return NO; - } - } - return YES; -} - -- (NSString *)defaultAccessibilityLabel -{ - ASLockScopeSelf(); - return _attributedText.string; -} - -- (UIAccessibilityTraits)defaultAccessibilityTraits -{ - return UIAccessibilityTraitStaticText; -} - -#pragma mark - Layout and Sizing - -- (void)setTextContainerInset:(UIEdgeInsets)textContainerInset -{ - ASLockScopeSelf(); - if (ASCompareAssignCustom(_textContainer.insets, textContainerInset, UIEdgeInsetsEqualToEdgeInsets)) { - [self setNeedsLayout]; - } -} - -- (UIEdgeInsets)textContainerInset -{ - // textContainer is invariant and has an atomic accessor. - return _textContainer.insets; -} - -- (void)setTextContainerLinePositionModifier:(id)modifier -{ - ASLockedSelfCompareAssignObjects(_textContainer.linePositionModifier, modifier); -} - -- (id)textContainerLinePositionModifier -{ - ASLockScopeSelf(); - return _textContainer.linePositionModifier; -} - -- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize -{ - ASDisplayNodeAssert(constrainedSize.width >= 0, @"Constrained width for text (%f) is too narrow", constrainedSize.width); - ASDisplayNodeAssert(constrainedSize.height >= 0, @"Constrained height for text (%f) is too short", constrainedSize.height); - - ASLockScopeSelf(); - - _textContainer.size = constrainedSize; - [self _ensureTruncationText]; - - // If the constrained size has a max/inf value on the text's forward direction, the text node is calculating its intrinsic size. - // Need to consider both width and height when determining if it is calculating instrinsic size. Even the constrained width is provided, the height can be inf - // it may provide a text that is longer than the width and require a wordWrapping line break mode and looking for the height to be calculated. - BOOL isCalculatingIntrinsicSize = (_textContainer.size.width >= ASTextContainerMaxSize.width) || (_textContainer.size.height >= ASTextContainerMaxSize.height); - - NSMutableAttributedString *mutableText = [_attributedText mutableCopy]; - [self prepareAttributedString:mutableText isForIntrinsicSize:isCalculatingIntrinsicSize]; - ASTextLayout *layout = ASTextNodeCompatibleLayoutWithContainerAndText(_textContainer, mutableText); - if (layout.truncatedLine != nil && layout.truncatedLine.size.width > layout.textBoundingSize.width) { - return (CGSize) {MIN(constrainedSize.width, layout.truncatedLine.size.width), layout.textBoundingSize.height}; - } - - return layout.textBoundingSize; -} - -#pragma mark - Modifying User Text - -// Returns the ascender of the first character in attributedString by also including the line height if specified in paragraph style. -+ (CGFloat)ascenderWithAttributedString:(NSAttributedString *)attributedString -{ - UIFont *font = [attributedString attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL]; - NSParagraphStyle *paragraphStyle = [attributedString attribute:NSParagraphStyleAttributeName atIndex:0 effectiveRange:NULL]; - if (!paragraphStyle) { - return font.ascender; - } - CGFloat lineHeight = MAX(font.lineHeight, paragraphStyle.minimumLineHeight); - if (paragraphStyle.maximumLineHeight > 0) { - lineHeight = MIN(lineHeight, paragraphStyle.maximumLineHeight); - } - return lineHeight + font.descender; -} - -- (NSAttributedString *)attributedText -{ - ASLockScopeSelf(); - return _attributedText; -} - -- (void)setAttributedText:(NSAttributedString *)attributedText -{ - if (attributedText == nil) { - attributedText = [[NSAttributedString alloc] initWithString:@"" attributes:nil]; - } - - // Many accessors in this method will acquire the lock (including ASDisplayNode methods). - // Holding it for the duration of the method is more efficient in this case. - ASLockScopeSelf(); - - if (!ASCompareAssignCopy(_attributedText, attributedText)) { - return; - } - - // Since truncation text matches style of attributedText, invalidate it now. - [self _locked_invalidateTruncationText]; - - NSUInteger length = attributedText.length; - if (length > 0) { - ASLayoutElementStyle *style = [self _locked_style]; - style.ascender = [[self class] ascenderWithAttributedString:attributedText]; - style.descender = [[attributedText attribute:NSFontAttributeName atIndex:attributedText.length - 1 effectiveRange:NULL] descender]; - } - - // Tell the display node superclasses that the cached layout is incorrect now - [self setNeedsLayout]; - - // Force display to create renderer with new size and redisplay with new string - [self setNeedsDisplay]; - - // Accessiblity - self.accessibilityLabel = self.defaultAccessibilityLabel; - self.isAccessibilityElement = (length != 0); // We're an accessibility element by default if there is a string. - -#if AS_TEXTNODE2_RECORD_ATTRIBUTED_STRINGS - [ASTextNode _registerAttributedText:_attributedText]; -#endif -} - -#pragma mark - Text Layout - -- (void)setExclusionPaths:(NSArray *)exclusionPaths -{ - ASLockScopeSelf(); - _textContainer.exclusionPaths = exclusionPaths; - - [self setNeedsLayout]; - [self setNeedsDisplay]; -} - -- (NSArray *)exclusionPaths -{ - ASLockScopeSelf(); - return _textContainer.exclusionPaths; -} - -- (void)prepareAttributedString:(NSMutableAttributedString *)attributedString isForIntrinsicSize:(BOOL)isForIntrinsicSize -{ - ASLockScopeSelf(); - NSLineBreakMode innerMode; - switch (_truncationMode) { - case NSLineBreakByWordWrapping: - case NSLineBreakByCharWrapping: - case NSLineBreakByClipping: - innerMode = _truncationMode; - break; - default: - innerMode = NSLineBreakByWordWrapping; - } - - // Apply/Fix paragraph style if needed - [attributedString enumerateAttribute:NSParagraphStyleAttributeName inRange:NSMakeRange(0, attributedString.length) options:kNilOptions usingBlock:^(NSParagraphStyle *style, NSRange range, BOOL * _Nonnull stop) { - - BOOL applyTruncationMode = YES; - NSMutableParagraphStyle *paragraphStyle = nil; - // Only "left" and "justified" alignments are supported while calculating intrinsic size. - // Other alignments like "right", "center" and "natural" cause the size to be bigger than needed and thus should be ignored/overridden. - const BOOL forceLeftAlignment = (style != nil - && isForIntrinsicSize - && style.alignment != NSTextAlignmentLeft - && style.alignment != NSTextAlignmentJustified); - if (style != nil) { - if (innerMode == style.lineBreakMode) { - applyTruncationMode = NO; - } - paragraphStyle = [style mutableCopy]; - } else { - if (innerMode == NSLineBreakByWordWrapping) { - applyTruncationMode = NO; - } - paragraphStyle = [NSMutableParagraphStyle new]; - } - if (!applyTruncationMode && !forceLeftAlignment) { - return; - } - paragraphStyle.lineBreakMode = innerMode; - - if (applyTruncationMode) { - paragraphStyle.lineBreakMode = _truncationMode; - } - if (forceLeftAlignment) { - paragraphStyle.alignment = NSTextAlignmentLeft; - } - [attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range]; - }]; - - // Apply shadow if needed - if (_shadowOpacity > 0 && (_shadowRadius != 0 || !CGSizeEqualToSize(_shadowOffset, CGSizeZero)) && CGColorGetAlpha(_shadowColor) > 0) { - NSShadow *shadow = [[NSShadow alloc] init]; - if (_shadowOpacity != 1) { - CGColorRef shadowColorRef = CGColorCreateCopyWithAlpha(_shadowColor, _shadowOpacity * CGColorGetAlpha(_shadowColor)); - shadow.shadowColor = [UIColor colorWithCGColor:shadowColorRef]; - CGColorRelease(shadowColorRef); - } else { - shadow.shadowColor = [UIColor colorWithCGColor:_shadowColor]; - } - shadow.shadowOffset = _shadowOffset; - shadow.shadowBlurRadius = _shadowRadius; - [attributedString addAttribute:NSShadowAttributeName value:shadow range:NSMakeRange(0, attributedString.length)]; - } -} - -#pragma mark - Drawing - -- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer -{ - ASLockScopeSelf(); - [self _ensureTruncationText]; - - // Unlike layout, here we must copy the container since drawing is asynchronous. - ASTextContainer *copiedContainer = [_textContainer copy]; - copiedContainer.size = self.bounds.size; - [copiedContainer makeImmutable]; - NSMutableAttributedString *mutableText = [_attributedText mutableCopy] ?: [[NSMutableAttributedString alloc] init]; - - [self prepareAttributedString:mutableText isForIntrinsicSize:NO]; - - return @{ - @"container": copiedContainer, - @"text": mutableText, - @"bgColor": self.backgroundColor ?: [NSNull null] - }; -} - -+ (void)drawRect:(CGRect)bounds withParameters:(NSDictionary *)layoutDict isCancelled:(NS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing -{ - ASTextContainer *container = layoutDict[@"container"]; - NSAttributedString *text = layoutDict[@"text"]; - UIColor *bgColor = layoutDict[@"bgColor"]; - ASTextLayout *layout = ASTextNodeCompatibleLayoutWithContainerAndText(container, text); - - if (isCancelledBlock()) { - return; - } - - // Fill background color. - if (bgColor == (id)[NSNull null]) { - bgColor = nil; - } - - // They may have already drawn into this context in the pre-context block - // so unfortunately we have to use the normal blend mode, not copy. - if (bgColor && CGColorGetAlpha(bgColor.CGColor) > 0) { - [bgColor setFill]; - UIRectFillUsingBlendMode(bounds, kCGBlendModeNormal); - } - - CGContextRef context = UIGraphicsGetCurrentContext(); - ASDisplayNodeAssert(context, @"This is no good without a context."); - - [layout drawInContext:context size:bounds.size point:bounds.origin view:nil layer:nil debug:[ASTextDebugOption sharedDebugOption] cancel:isCancelledBlock]; -} - -#pragma mark - Attributes - -- (id)linkAttributeValueAtPoint:(CGPoint)point - attributeName:(out NSString **)attributeNameOut - range:(out NSRange *)rangeOut -{ - return [self _linkAttributeValueAtPoint:point - attributeName:attributeNameOut - range:rangeOut - inAdditionalTruncationMessage:NULL - forHighlighting:NO]; -} - -- (id)_linkAttributeValueAtPoint:(CGPoint)point - attributeName:(out NSString **)attributeNameOut - range:(out NSRange *)rangeOut - inAdditionalTruncationMessage:(out BOOL *)inAdditionalTruncationMessageOut - forHighlighting:(BOOL)highlighting -{ - ASLockScopeSelf(); - - // TODO: The copy and application of size shouldn't be required, but it is currently. - // See discussion in https://github.com/TextureGroup/Texture/pull/396 - ASTextContainer *containerCopy = [_textContainer copy]; - containerCopy.size = self.calculatedSize; - ASTextLayout *layout = ASTextNodeCompatibleLayoutWithContainerAndText(containerCopy, _attributedText); - - if ([self _locked_pointInsideAdditionalTruncationMessage:point withLayout:layout]) { - if (inAdditionalTruncationMessageOut != NULL) { - *inAdditionalTruncationMessageOut = YES; - } - return nil; - } - - NSRange visibleRange = layout.visibleRange; - NSRange clampedRange = NSIntersectionRange(visibleRange, NSMakeRange(0, _attributedText.length)); - - // Search the 9 points of a 44x44 square around the touch until we find a link. - // Start from center, then do sides, then do top/bottom, then do corners. - static constexpr CGSize kRectOffsets[9] = { - { 0, 0 }, - { -22, 0 }, { 22, 0 }, - { 0, -22 }, { 0, 22 }, - { -22, -22 }, { -22, 22 }, - { 22, -22 }, { 22, 22 } - }; - - for (const CGSize &offset : kRectOffsets) { - const CGPoint testPoint = CGPointMake(point.x + offset.width, - point.y + offset.height); - ASTextPosition *pos = [layout closestPositionToPoint:testPoint]; - if (!pos || !NSLocationInRange(pos.offset, clampedRange)) { - continue; - } - for (NSString *attributeName in _linkAttributeNames) { - NSRange effectiveRange = NSMakeRange(0, 0); - id value = [_attributedText attribute:attributeName atIndex:pos.offset - longestEffectiveRange:&effectiveRange inRange:clampedRange]; - if (value == nil) { - // Didn't find any links specified with this attribute. - continue; - } - - // If highlighting, check with delegate first. If not implemented, assume YES. - if (highlighting - && [_delegate respondsToSelector:@selector(textNode:shouldHighlightLinkAttribute:value:atPoint:)] - && ![_delegate textNode:(ASTextNode *)self shouldHighlightLinkAttribute:attributeName - value:value atPoint:point]) { - continue; - } - - *rangeOut = NSIntersectionRange(visibleRange, effectiveRange); - - if (attributeNameOut != NULL) { - *attributeNameOut = attributeName; - } - - return value; - } - } - - return nil; -} - -- (BOOL)_locked_pointInsideAdditionalTruncationMessage:(CGPoint)point withLayout:(ASTextLayout *)layout -{ - // Check if the range is within the additional truncation range - BOOL inAdditionalTruncationMessage = NO; - - CTLineRef truncatedCTLine = layout.truncatedLine.CTLine; - if (truncatedCTLine != NULL && _additionalTruncationMessage != nil) { - CFIndex stringIndexForPosition = CTLineGetStringIndexForPosition(truncatedCTLine, point); - if (stringIndexForPosition != kCFNotFound) { - CFIndex truncatedCTLineGlyphCount = CTLineGetGlyphCount(truncatedCTLine); - - CTLineRef truncationTokenLine = CTLineCreateWithAttributedString((CFAttributedStringRef)_truncationAttributedText); - CFIndex truncationTokenLineGlyphCount = truncationTokenLine ? CTLineGetGlyphCount(truncationTokenLine) : 0; - CFRelease(truncationTokenLine); - - CTLineRef additionalTruncationTokenLine = CTLineCreateWithAttributedString((CFAttributedStringRef)_additionalTruncationMessage); - CFIndex additionalTruncationTokenLineGlyphCount = additionalTruncationTokenLine ? CTLineGetGlyphCount(additionalTruncationTokenLine) : 0; - CFRelease(additionalTruncationTokenLine); - - switch (_textContainer.truncationType) { - case ASTextTruncationTypeStart: { - CFIndex composedTruncationTextLineGlyphCount = truncationTokenLineGlyphCount + additionalTruncationTokenLineGlyphCount; - if (stringIndexForPosition > truncationTokenLineGlyphCount && - stringIndexForPosition < composedTruncationTextLineGlyphCount) { - inAdditionalTruncationMessage = YES; - } - break; - } - case ASTextTruncationTypeMiddle: { - CFIndex composedTruncationTextLineGlyphCount = truncationTokenLineGlyphCount + additionalTruncationTokenLineGlyphCount; - CFIndex firstTruncatedTokenIndex = (truncatedCTLineGlyphCount - composedTruncationTextLineGlyphCount) / 2.0; - if ((firstTruncatedTokenIndex + truncationTokenLineGlyphCount) < stringIndexForPosition && - stringIndexForPosition < (firstTruncatedTokenIndex + composedTruncationTextLineGlyphCount)) { - inAdditionalTruncationMessage = YES; - } - break; - } - case ASTextTruncationTypeEnd: { - if (stringIndexForPosition > (truncatedCTLineGlyphCount - additionalTruncationTokenLineGlyphCount)) { - inAdditionalTruncationMessage = YES; - } - break; - } - default: - // For now, assume that a tap inside this text, but outside the text range is a tap on the - // truncation token. - if (![layout textRangeAtPoint:point]) { - inAdditionalTruncationMessage = YES; - } - break; - } - } - } - - return inAdditionalTruncationMessage; -} - -#pragma mark - UIGestureRecognizerDelegate - -- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer -{ - ASDisplayNodeAssertMainThread(); - ASLockScopeSelf(); // Protect usage of _highlight* ivars. - - if (gestureRecognizer == _longPressGestureRecognizer) { - // Don't allow long press on truncation message - if ([self _pendingTruncationTap]) { - return NO; - } - - // Ask our delegate if a long-press on an attribute is relevant - id delegate = self.delegate; - if ([delegate respondsToSelector:@selector(textNode:shouldLongPressLinkAttribute:value:atPoint:)]) { - return [delegate textNode:(ASTextNode *)self - shouldLongPressLinkAttribute:_highlightedLinkAttributeName - value:_highlightedLinkAttributeValue - atPoint:[gestureRecognizer locationInView:self.view]]; - } - - // Otherwise we are good to go. - return YES; - } - - if (([self _pendingLinkTap] || [self _pendingTruncationTap]) - && [gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]] - && CGRectContainsPoint(self.threadSafeBounds, [gestureRecognizer locationInView:self.view])) { - return NO; - } - - return [super gestureRecognizerShouldBegin:gestureRecognizer]; -} - -#pragma mark - Highlighting - -- (ASTextNodeHighlightStyle)highlightStyle -{ - ASLockScopeSelf(); - - return _highlightStyle; -} - -- (void)setHighlightStyle:(ASTextNodeHighlightStyle)highlightStyle -{ - ASLockScopeSelf(); - - _highlightStyle = highlightStyle; -} - -- (NSRange)highlightRange -{ - ASLockScopeSelf(); - - return _highlightRange; -} - -- (void)setHighlightRange:(NSRange)highlightRange -{ - [self setHighlightRange:highlightRange animated:NO]; -} - -- (void)setHighlightRange:(NSRange)highlightRange animated:(BOOL)animated -{ - [self _setHighlightRange:highlightRange forAttributeName:nil value:nil animated:animated]; -} - -- (void)_setHighlightRange:(NSRange)highlightRange forAttributeName:(NSString *)highlightedAttributeName value:(id)highlightedAttributeValue animated:(BOOL)animated -{ - ASLockScopeSelf(); // Protect usage of _highlight* ivars. - - // Set these so that link tapping works. - _highlightedLinkAttributeName = highlightedAttributeName; - _highlightedLinkAttributeValue = highlightedAttributeValue; - _highlightRange = highlightRange; - - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - // Much of the code from original ASTextNode is probably usable here. - - return; -} - -- (void)_clearHighlightIfNecessary -{ - ASDisplayNodeAssertMainThread(); - - if ([self _pendingLinkTap] || [self _pendingTruncationTap]) { - [self setHighlightRange:NSMakeRange(0, 0) animated:YES]; - } -} - -+ (CGColorRef)_highlightColorForStyle:(ASTextNodeHighlightStyle)style -{ - return [UIColor colorWithWhite:(style == ASTextNodeHighlightStyleLight ? 0.0 : 1.0) alpha:1.0].CGColor; -} - -+ (CGFloat)_highlightOpacityForStyle:(ASTextNodeHighlightStyle)style -{ - return (style == ASTextNodeHighlightStyleLight) ? ASTextNodeHighlightLightOpacity : ASTextNodeHighlightDarkOpacity; -} - -#pragma mark - Text rects - -- (NSArray *)rectsForTextRange:(NSRange)textRange -{ - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - return @[]; -} - -- (NSArray *)highlightRectsForTextRange:(NSRange)textRange -{ - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - return @[]; -} - -- (CGRect)trailingRect -{ - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - return CGRectZero; -} - -- (CGRect)frameForTextRange:(NSRange)textRange -{ - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - return CGRectZero; -} - -#pragma mark - Placeholders - -- (UIColor *)placeholderColor -{ - return ASLockedSelf(_placeholderColor); -} - -- (void)setPlaceholderColor:(UIColor *)placeholderColor -{ - ASLockScopeSelf(); - if (ASCompareAssignCopy(_placeholderColor, placeholderColor)) { - self.placeholderEnabled = CGColorGetAlpha(placeholderColor.CGColor) > 0; - } -} - -- (UIImage *)placeholderImage -{ - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - return nil; -} - -#pragma mark - Touch Handling - -- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - - if (!_passthroughNonlinkTouches) { - return [super pointInside:point withEvent:event]; - } - - NSRange range = NSMakeRange(0, 0); - NSString *linkAttributeName = nil; - BOOL inAdditionalTruncationMessage = NO; - - id linkAttributeValue = [self _linkAttributeValueAtPoint:point - attributeName:&linkAttributeName - range:&range - inAdditionalTruncationMessage:&inAdditionalTruncationMessage - forHighlighting:YES]; - - NSUInteger lastCharIndex = NSIntegerMax; - BOOL linkCrossesVisibleRange = (lastCharIndex > range.location) && (lastCharIndex < NSMaxRange(range) - 1); - - if (range.length > 0 && !linkCrossesVisibleRange && linkAttributeValue != nil && linkAttributeName != nil) { - return YES; - } else { - return NO; - } -} - -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - - [super touchesBegan:touches withEvent:event]; - - CGPoint point = [[touches anyObject] locationInView:self.view]; - - NSRange range = NSMakeRange(0, 0); - NSString *linkAttributeName = nil; - BOOL inAdditionalTruncationMessage = NO; - - id linkAttributeValue = [self _linkAttributeValueAtPoint:point - attributeName:&linkAttributeName - range:&range - inAdditionalTruncationMessage:&inAdditionalTruncationMessage - forHighlighting:YES]; - - NSUInteger lastCharIndex = NSIntegerMax; - BOOL linkCrossesVisibleRange = (lastCharIndex > range.location) && (lastCharIndex < NSMaxRange(range) - 1); - - if (inAdditionalTruncationMessage) { - NSRange visibleRange = NSMakeRange(0, 0); - { - ASLockScopeSelf(); - // TODO: The copy and application of size shouldn't be required, but it is currently. - // See discussion in https://github.com/TextureGroup/Texture/pull/396 - ASTextContainer *containerCopy = [_textContainer copy]; - containerCopy.size = self.calculatedSize; - ASTextLayout *layout = ASTextNodeCompatibleLayoutWithContainerAndText(containerCopy, _attributedText); - visibleRange = layout.visibleRange; - } - NSRange truncationMessageRange = [self _additionalTruncationMessageRangeWithVisibleRange:visibleRange]; - [self _setHighlightRange:truncationMessageRange forAttributeName:ASTextNodeTruncationTokenAttributeName value:nil animated:YES]; - } else if (range.length > 0 && !linkCrossesVisibleRange && linkAttributeValue != nil && linkAttributeName != nil) { - [self _setHighlightRange:range forAttributeName:linkAttributeName value:linkAttributeValue animated:YES]; - } - - return; -} - - -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - [super touchesCancelled:touches withEvent:event]; - - [self _clearHighlightIfNecessary]; -} - -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - [super touchesEnded:touches withEvent:event]; - - ASLockScopeSelf(); // Protect usage of _highlight* ivars. - id delegate = self.delegate; - if ([self _pendingLinkTap] && [delegate respondsToSelector:@selector(textNode:tappedLinkAttribute:value:atPoint:textRange:)]) { - CGPoint point = [[touches anyObject] locationInView:self.view]; - [delegate textNode:(ASTextNode *)self tappedLinkAttribute:_highlightedLinkAttributeName value:_highlightedLinkAttributeValue atPoint:point textRange:_highlightRange]; - } - - if ([self _pendingTruncationTap]) { - if ([delegate respondsToSelector:@selector(textNodeTappedTruncationToken:)]) { - [delegate textNodeTappedTruncationToken:(ASTextNode *)self]; - } - } - - [self _clearHighlightIfNecessary]; -} - -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - [super touchesMoved:touches withEvent:event]; - - ASLockScopeSelf(); // Protect usage of _highlight* ivars. - UITouch *touch = [touches anyObject]; - CGPoint locationInView = [touch locationInView:self.view]; - // on 3D Touch enabled phones, this gets fired with changes in force, and usually will get fired immediately after touchesBegan:withEvent: - if (CGPointEqualToPoint([touch previousLocationInView:self.view], locationInView)) - return; - - // If touch has moved out of the current highlight range, clear the highlight. - if (_highlightRange.length > 0) { - NSRange range = NSMakeRange(0, 0); - [self _linkAttributeValueAtPoint:locationInView - attributeName:NULL - range:&range - inAdditionalTruncationMessage:NULL - forHighlighting:YES]; - - if (!NSEqualRanges(_highlightRange, range)) { - [self _clearHighlightIfNecessary]; - } - } -} - -- (void)_handleLongPress:(UILongPressGestureRecognizer *)longPressRecognizer -{ - ASDisplayNodeAssertMainThread(); - - // Respond to long-press when it begins, not when it ends. - if (longPressRecognizer.state == UIGestureRecognizerStateBegan) { - id delegate = self.delegate; - if ([delegate respondsToSelector:@selector(textNode:longPressedLinkAttribute:value:atPoint:textRange:)]) { - ASLockScopeSelf(); // Protect usage of _highlight* ivars. - CGPoint touchPoint = [_longPressGestureRecognizer locationInView:self.view]; - [delegate textNode:(ASTextNode *)self longPressedLinkAttribute:_highlightedLinkAttributeName value:_highlightedLinkAttributeValue atPoint:touchPoint textRange:_highlightRange]; - } - } -} - -- (BOOL)_pendingLinkTap -{ - ASLockScopeSelf(); - - return (_highlightedLinkAttributeValue != nil && ![self _pendingTruncationTap]) && self.delegate != nil; -} - -- (BOOL)_pendingTruncationTap -{ - return [ASLockedSelf(_highlightedLinkAttributeName) isEqualToString:ASTextNodeTruncationTokenAttributeName]; -} - -#pragma mark - Shadow Properties - -/** - * Note about shadowed text: - * - * Shadowed text is pretty rare, and we are a framework that targets serious developers. - * We should probably ignore these properties and tell developers to set the shadow into their attributed text instead. - */ -- (CGColorRef)shadowColor -{ - return ASLockedSelf(_shadowColor); -} - -- (void)setShadowColor:(CGColorRef)shadowColor -{ - ASLockScopeSelf(); - if (_shadowColor != shadowColor && CGColorEqualToColor(shadowColor, _shadowColor) == NO) { - CGColorRelease(_shadowColor); - _shadowColor = CGColorRetain(shadowColor); - [self setNeedsDisplay]; - } -} - -- (CGSize)shadowOffset -{ - return ASLockedSelf(_shadowOffset); -} - -- (void)setShadowOffset:(CGSize)shadowOffset -{ - ASLockScopeSelf(); - if (ASCompareAssignCustom(_shadowOffset, shadowOffset, CGSizeEqualToSize)) { - [self setNeedsDisplay]; - } -} - -- (CGFloat)shadowOpacity -{ - return ASLockedSelf(_shadowOpacity); -} - -- (void)setShadowOpacity:(CGFloat)shadowOpacity -{ - ASLockScopeSelf(); - if (ASCompareAssign(_shadowOpacity, shadowOpacity)) { - [self setNeedsDisplay]; - } -} - -- (CGFloat)shadowRadius -{ - return ASLockedSelf(_shadowRadius); -} - -- (void)setShadowRadius:(CGFloat)shadowRadius -{ - ASLockScopeSelf(); - if (ASCompareAssign(_shadowRadius, shadowRadius)) { - [self setNeedsDisplay]; - } -} - -- (UIEdgeInsets)shadowPadding -{ - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - return UIEdgeInsetsZero; -} - -- (void)setPointSizeScaleFactors:(NSArray *)scaleFactors -{ - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - ASLockScopeSelf(); - if (ASCompareAssignCopy(_pointSizeScaleFactors, scaleFactors)) { - [self setNeedsLayout]; - } -} - -- (NSArray *)pointSizeScaleFactors -{ - return ASLockedSelf(_pointSizeScaleFactors); -} - -#pragma mark - Truncation Message - -static NSAttributedString *DefaultTruncationAttributedString() -{ - static NSAttributedString *defaultTruncationAttributedString; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - defaultTruncationAttributedString = [[NSAttributedString alloc] initWithString:NSLocalizedString(@"\u2026", @"Default truncation string")]; - }); - return defaultTruncationAttributedString; -} - -- (void)_ensureTruncationText -{ - ASLockScopeSelf(); - if (_textContainer.truncationToken == nil) { - _textContainer.truncationToken = [self _locked_composedTruncationText]; - } -} - -- (NSAttributedString *)truncationAttributedText -{ - return ASLockedSelf(_truncationAttributedText); -} - -- (void)setTruncationAttributedText:(NSAttributedString *)truncationAttributedText -{ - ASLockScopeSelf(); - if (ASCompareAssignCopy(_truncationAttributedText, truncationAttributedText)) { - [self _invalidateTruncationText]; - } -} - -- (NSAttributedString *)additionalTruncationMessage -{ - return ASLockedSelf(_additionalTruncationMessage); -} - -- (void)setAdditionalTruncationMessage:(NSAttributedString *)additionalTruncationMessage -{ - ASLockScopeSelf(); - if (ASCompareAssignCopy(_additionalTruncationMessage, additionalTruncationMessage)) { - [self _invalidateTruncationText]; - } -} - -- (NSLineBreakMode)truncationMode -{ - return ASLockedSelf(_truncationMode); -} - -- (void)setTruncationMode:(NSLineBreakMode)truncationMode -{ - ASLockScopeSelf(); - if (ASCompareAssign(_truncationMode, truncationMode)) { - ASTextTruncationType truncationType; - switch (truncationMode) { - case NSLineBreakByTruncatingHead: - truncationType = ASTextTruncationTypeStart; - break; - case NSLineBreakByTruncatingTail: - truncationType = ASTextTruncationTypeEnd; - break; - case NSLineBreakByTruncatingMiddle: - truncationType = ASTextTruncationTypeMiddle; - break; - default: - truncationType = ASTextTruncationTypeNone; - } - - _textContainer.truncationType = truncationType; - - [self setNeedsDisplay]; - } -} - -- (BOOL)isTruncated -{ - return ASLockedSelf([self locked_textLayoutForSize:[self _locked_threadSafeBounds].size].truncatedLine != nil); -} - -- (BOOL)shouldTruncateForConstrainedSize:(ASSizeRange)constrainedSize -{ - return ASLockedSelf([self locked_textLayoutForSize:constrainedSize.max].truncatedLine != nil); -} - -- (ASTextLayout *)locked_textLayoutForSize:(CGSize)size -{ - ASTextContainer *container = [_textContainer copy]; - container.size = size; - return ASTextNodeCompatibleLayoutWithContainerAndText(container, _attributedText); -} - -- (NSUInteger)maximumNumberOfLines -{ - // _textContainer is invariant and this is just atomic access. - return _textContainer.maximumNumberOfRows; -} - -- (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines -{ - ASLockScopeSelf(); - if (ASCompareAssign(_textContainer.maximumNumberOfRows, maximumNumberOfLines)) { - [self setNeedsDisplay]; - } -} - -- (NSUInteger)lineCount -{ - ASLockScopeSelf(); - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - return 0; -} - -#pragma mark - Truncation Message - -- (void)_invalidateTruncationText -{ - ASLockScopeSelf(); - [self _locked_invalidateTruncationText]; - [self setNeedsDisplay]; -} - -- (void)_locked_invalidateTruncationText -{ - _textContainer.truncationToken = nil; -} - -/** - * @return the additional truncation message range within the as-rendered text. - * Must be called from main thread - */ -- (NSRange)_additionalTruncationMessageRangeWithVisibleRange:(NSRange)visibleRange -{ - ASLockScopeSelf(); - - // Check if we even have an additional truncation message. - if (!_additionalTruncationMessage) { - return NSMakeRange(NSNotFound, 0); - } - - // Character location of the unicode ellipsis (the first index after the visible range) - NSInteger truncationTokenIndex = NSMaxRange(visibleRange); - - NSUInteger additionalTruncationMessageLength = _additionalTruncationMessage.length; - // We get the location of the truncation token, then add the length of the - // truncation attributed string +1 for the space between. - return NSMakeRange(truncationTokenIndex + _truncationAttributedText.length + 1, additionalTruncationMessageLength); -} - -/** - * @return the truncation message for the string. If there are both an - * additional truncation message and a truncation attributed string, they will - * be properly composed. - */ -- (NSAttributedString *)_locked_composedTruncationText -{ - ASAssertLocked(__instanceLock__); - if (_composedTruncationText == nil) { - if (_truncationAttributedText != nil && _additionalTruncationMessage != nil) { - NSMutableAttributedString *newComposedTruncationString = [[NSMutableAttributedString alloc] initWithAttributedString:_truncationAttributedText]; - [newComposedTruncationString.mutableString appendString:@" "]; - [newComposedTruncationString appendAttributedString:_additionalTruncationMessage]; - _composedTruncationText = newComposedTruncationString; - } else if (_truncationAttributedText != nil) { - _composedTruncationText = _truncationAttributedText; - } else if (_additionalTruncationMessage != nil) { - _composedTruncationText = _additionalTruncationMessage; - } else { - _composedTruncationText = DefaultTruncationAttributedString(); - } - _composedTruncationText = [self _locked_prepareTruncationStringForDrawing:_composedTruncationText]; - } - return _composedTruncationText; -} - -/** - * - cleanses it of core text attributes so TextKit doesn't crash - * - Adds whole-string attributes so the truncation message matches the styling - * of the body text - */ -- (NSAttributedString *)_locked_prepareTruncationStringForDrawing:(NSAttributedString *)truncationString -{ - ASAssertLocked(__instanceLock__); - NSMutableAttributedString *truncationMutableString = [truncationString mutableCopy]; - // Grab the attributes from the full string - if (_attributedText.length > 0) { - NSAttributedString *originalString = _attributedText; - NSInteger originalStringLength = _attributedText.length; - // Add any of the original string's attributes to the truncation string, - // but don't overwrite any of the truncation string's attributes - NSDictionary *originalStringAttributes = [originalString attributesAtIndex:originalStringLength-1 effectiveRange:NULL]; - [truncationString enumerateAttributesInRange:NSMakeRange(0, truncationString.length) options:0 usingBlock: - ^(NSDictionary *attributes, NSRange range, BOOL *stop) { - NSMutableDictionary *futureTruncationAttributes = [originalStringAttributes mutableCopy]; - [futureTruncationAttributes addEntriesFromDictionary:attributes]; - [truncationMutableString setAttributes:futureTruncationAttributes range:range]; - }]; - } - return truncationMutableString; -} - -#if AS_TEXTNODE2_RECORD_ATTRIBUTED_STRINGS -+ (void)_registerAttributedText:(NSAttributedString *)str -{ - static NSMutableArray *array; - static NSLock *lock; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - lock = [NSLock new]; - array = [NSMutableArray new]; - }); - [lock lock]; - [array addObject:str]; - if (array.count % 20 == 0) { - NSLog(@"Got %d strings", (int)array.count); - } - if (array.count == 2000) { - NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"AttributedStrings.plist"]; - NSAssert([NSKeyedArchiver archiveRootObject:array toFile:path], nil); - NSLog(@"Saved to %@", path); - } - [lock unlock]; -} -#endif - -+ (void)enableDebugging -{ - ASTextDebugOption *debugOption = [[ASTextDebugOption alloc] init]; - debugOption.CTLineFillColor = [UIColor colorWithRed:0 green:0.3 blue:1 alpha:0.1]; - [ASTextDebugOption setSharedDebugOption:debugOption]; -} - -- (BOOL)usingExperiment -{ - return YES; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASTextNodeCommon.h b/submodules/AsyncDisplayKit/Source/ASTextNodeCommon.h index ab4e134bfd..765f401c2a 100644 --- a/submodules/AsyncDisplayKit/Source/ASTextNodeCommon.h +++ b/submodules/AsyncDisplayKit/Source/ASTextNodeCommon.h @@ -10,8 +10,6 @@ #import -@class ASTextNode; - #define AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE() { \ static dispatch_once_t onceToken; \ dispatch_once(&onceToken, ^{ \ @@ -34,58 +32,3 @@ typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) { ASTextNodeHighlightStyleDark }; -/** - * @abstract Text node delegate. - */ -@protocol ASTextNodeDelegate -@optional - -/** - @abstract Indicates to the delegate that a link was tapped within a text node. - @param textNode The ASTextNode containing the link that was tapped. - @param attribute The attribute that was tapped. Will not be nil. - @param value The value of the tapped attribute. - @param point The point within textNode, in textNode's coordinate system, that was tapped. - @param textRange The range of highlighted text. - */ -- (void)textNode:(ASTextNode *)textNode tappedLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point textRange:(NSRange)textRange; - -/** - @abstract Indicates to the delegate that a link was tapped within a text node. - @param textNode The ASTextNode containing the link that was tapped. - @param attribute The attribute that was tapped. Will not be nil. - @param value The value of the tapped attribute. - @param point The point within textNode, in textNode's coordinate system, that was tapped. - @param textRange The range of highlighted text. - @discussion In addition to implementing this method, the delegate must be set on the text - node before it is loaded (the recognizer is created in -didLoad) - */ -- (void)textNode:(ASTextNode *)textNode longPressedLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point textRange:(NSRange)textRange; - -//! @abstract Called when the text node's truncation string has been tapped. -- (void)textNodeTappedTruncationToken:(ASTextNode *)textNode; - -/** - @abstract Indicates to the text node if an attribute should be considered a link. - @param textNode The text node containing the entity attribute. - @param attribute The attribute that was tapped. Will not be nil. - @param value The value of the tapped attribute. - @param point The point within textNode, in textNode's coordinate system, that was touched to trigger a highlight. - @discussion If not implemented, the default value is YES. - @return YES if the entity attribute should be a link, NO otherwise. - */ -- (BOOL)textNode:(ASTextNode *)textNode shouldHighlightLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point; - -/** - @abstract Indicates to the text node if an attribute is a valid long-press target - @param textNode The text node containing the entity attribute. - @param attribute The attribute that was tapped. Will not be nil. - @param value The value of the tapped attribute. - @param point The point within textNode, in textNode's coordinate system, that was long-pressed. - @discussion If not implemented, the default value is NO. - @return YES if the entity attribute should be treated as a long-press target, NO otherwise. - */ -- (BOOL)textNode:(ASTextNode *)textNode shouldLongPressLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point; - -@end - diff --git a/submodules/AsyncDisplayKit/Source/TextKit/ASTextNodeWordKerner.h b/submodules/AsyncDisplayKit/Source/ASTextNodeWordKerner.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/TextKit/ASTextNodeWordKerner.h rename to submodules/AsyncDisplayKit/Source/ASTextNodeWordKerner.h diff --git a/submodules/AsyncDisplayKit/Source/TextKit/ASTextNodeWordKerner.mm b/submodules/AsyncDisplayKit/Source/ASTextNodeWordKerner.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/TextKit/ASTextNodeWordKerner.mm rename to submodules/AsyncDisplayKit/Source/ASTextNodeWordKerner.mm index 67e640557c..e1d0c73c0e 100644 --- a/submodules/AsyncDisplayKit/Source/TextKit/ASTextNodeWordKerner.mm +++ b/submodules/AsyncDisplayKit/Source/ASTextNodeWordKerner.mm @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import +#import "ASTextNodeWordKerner.h" #import diff --git a/submodules/AsyncDisplayKit/Source/Details/ASTraitCollection.mm b/submodules/AsyncDisplayKit/Source/ASTraitCollection.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/Details/ASTraitCollection.mm rename to submodules/AsyncDisplayKit/Source/ASTraitCollection.mm diff --git a/submodules/AsyncDisplayKit/Source/ASVideoNode.h b/submodules/AsyncDisplayKit/Source/ASVideoNode.h deleted file mode 100644 index 5c38a6f641..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASVideoNode.h +++ /dev/null @@ -1,173 +0,0 @@ -// -// ASVideoNode.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK -#import -#import - -#if AS_USE_VIDEO - -@class AVAsset, AVPlayer, AVPlayerLayer, AVPlayerItem, AVVideoComposition, AVAudioMix; -@protocol ASVideoNodeDelegate; - -typedef NS_ENUM(NSInteger, ASVideoNodePlayerState) { - ASVideoNodePlayerStateUnknown, - ASVideoNodePlayerStateInitialLoading, - ASVideoNodePlayerStateReadyToPlay, - ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying, - ASVideoNodePlayerStatePlaying, - ASVideoNodePlayerStateLoading, - ASVideoNodePlayerStatePaused, - ASVideoNodePlayerStateFinished -}; - -NS_ASSUME_NONNULL_BEGIN - -// IMPORTANT NOTES: -// 1. Applications using ASVideoNode must link AVFoundation! (this provides the AV* classes below) -// 2. This is a relatively new component of AsyncDisplayKit. It has many useful features, but -// there is room for further expansion and optimization. Please report any issues or requests -// in an issue on GitHub: https://github.com/facebook/AsyncDisplayKit/issues - -@interface ASVideoNode : ASNetworkImageNode - -- (void)play; -- (void)pause; -- (BOOL)isPlaying; -- (void)resetToPlaceholder; - -// TODO: copy -@property (nullable) AVAsset *asset; - -/** - ** @abstract The URL with which the asset was initialized. - ** @discussion Setting the URL will override the current asset with a newly created AVURLAsset created from the given URL, and AVAsset *asset will point to that newly created AVURLAsset. Please don't set both assetURL and asset. - ** @return Current URL the asset was initialized or nil if no URL was given. - **/ -@property (nullable, copy) NSURL *assetURL; - -// TODO: copy both of these. -@property (nullable) AVVideoComposition *videoComposition; -@property (nullable) AVAudioMix *audioMix; - -@property (nullable, readonly) AVPlayer *player; - -// TODO: copy -@property (nullable, readonly) AVPlayerItem *currentItem; - -@property (nullable, nonatomic, readonly) AVPlayerLayer *playerLayer; - - -/** - * When shouldAutoplay is set to true, a video node will play when it has both loaded and entered the "visible" interfaceState. - * If it leaves the visible interfaceState it will pause but will resume once it has returned. - */ -@property BOOL shouldAutoplay; -@property BOOL shouldAutorepeat; - -@property BOOL muted; -@property BOOL shouldAggressivelyRecoverFromStall; - -@property (readonly) ASVideoNodePlayerState playerState; -//! Defaults to 1000 -@property int32_t periodicTimeObserverTimescale; - -//! Defaults to AVLayerVideoGravityResizeAspect -@property (null_resettable, copy) NSString *gravity; - -@property (nullable, weak) id delegate; - -@end - -@protocol ASVideoNodeDelegate -@optional -/** - * @abstract Delegate method invoked when the node's video has played to its end time. - * @param videoNode The video node has played to its end time. - */ -- (void)videoDidPlayToEnd:(ASVideoNode *)videoNode; -/** - * @abstract Delegate method invoked the node is tapped. - * @param videoNode The video node that was tapped. - * @discussion The video's play state is toggled if this method is not implemented. - */ -- (void)didTapVideoNode:(ASVideoNode *)videoNode; -/** - * @abstract Delegate method invoked when player changes state. - * @param videoNode The video node. - * @param state player state before this change. - * @param toState player new state. - * @discussion This method is called after each state change - */ -- (void)videoNode:(ASVideoNode *)videoNode willChangePlayerState:(ASVideoNodePlayerState)state toState:(ASVideoNodePlayerState)toState; -/** - * @abstract Ssks delegate if state change is allowed - * ASVideoNodePlayerStatePlaying or ASVideoNodePlayerStatePaused. - * asks delegate if state change is allowed. - * @param videoNode The video node. - * @param state player state that is going to be set. - * @discussion Delegate method invoked when player changes it's state to - * ASVideoNodePlayerStatePlaying or ASVideoNodePlayerStatePaused - * and asks delegate if state change is valid - */ -- (BOOL)videoNode:(ASVideoNode*)videoNode shouldChangePlayerStateTo:(ASVideoNodePlayerState)state; -/** - * @abstract Delegate method invoked when player playback time is updated. - * @param videoNode The video node. - * @param timeInterval current playback time in seconds. - */ -- (void)videoNode:(ASVideoNode *)videoNode didPlayToTimeInterval:(NSTimeInterval)timeInterval; -/** - * @abstract Delegate method invoked when the video player stalls. - * @param videoNode The video node that has experienced the stall - * @param timeInterval Current playback time when the stall happens - */ -- (void)videoNode:(ASVideoNode *)videoNode didStallAtTimeInterval:(NSTimeInterval)timeInterval; -/** - * @abstract Delegate method invoked when the video player starts the inital asset loading - * @param videoNode The videoNode - */ -- (void)videoNodeDidStartInitialLoading:(ASVideoNode *)videoNode; -/** - * @abstract Delegate method invoked when the video is done loading the asset and can start the playback - * @param videoNode The videoNode - */ -- (void)videoNodeDidFinishInitialLoading:(ASVideoNode *)videoNode; -/** - * @abstract Delegate method invoked when the AVPlayerItem for the asset has been set up and can be accessed throught currentItem. - * @param videoNode The videoNode. - * @param currentItem The AVPlayerItem that was constructed from the asset. - */ -- (void)videoNode:(ASVideoNode *)videoNode didSetCurrentItem:(AVPlayerItem *)currentItem; -/** - * @abstract Delegate method invoked when the video node has recovered from the stall - * @param videoNode The videoNode - */ -- (void)videoNodeDidRecoverFromStall:(ASVideoNode *)videoNode; -/** - * @abstract Delegate method invoked when an error occurs while trying to load an asset - * @param videoNode The videoNode. - * @param key The key of value that failed to load. - * @param asset The asset. - * @param error The error that occurs. - */ -- (void)videoNode:(ASVideoNode *)videoNode didFailToLoadValueForKey:(NSString *)key asset:(AVAsset *)asset error:(NSError *)error; - -@end - -@interface ASVideoNode (Unavailable) - -- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END -#endif - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASVideoNode.mm b/submodules/AsyncDisplayKit/Source/ASVideoNode.mm deleted file mode 100644 index 05478d91d6..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASVideoNode.mm +++ /dev/null @@ -1,864 +0,0 @@ -// -// ASVideoNode.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// -#ifndef MINIMAL_ASDK - -#import - -#if AS_USE_VIDEO - -#import -#import -#import -#import -#import -#import -#import -#import - -static BOOL ASAssetIsEqual(AVAsset *asset1, AVAsset *asset2) { - return ASObjectIsEqual(asset1, asset2) - || ([asset1 isKindOfClass:[AVURLAsset class]] - && [asset2 isKindOfClass:[AVURLAsset class]] - && ASObjectIsEqual(((AVURLAsset *)asset1).URL, ((AVURLAsset *)asset2).URL)); -} - -static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { - if ([videoGravity isEqualToString:AVLayerVideoGravityResizeAspectFill]) { - return UIViewContentModeScaleAspectFill; - } else if ([videoGravity isEqualToString:AVLayerVideoGravityResize]) { - return UIViewContentModeScaleToFill; - } else { - return UIViewContentModeScaleAspectFit; - } -} - -static void *ASVideoNodeContext = &ASVideoNodeContext; -static NSString * const kPlaybackLikelyToKeepUpKey = @"playbackLikelyToKeepUp"; -static NSString * const kplaybackBufferEmpty = @"playbackBufferEmpty"; -static NSString * const kStatus = @"status"; -static NSString * const kRate = @"rate"; - -@interface ASVideoNode () -{ - struct { - unsigned int delegateVideNodeShouldChangePlayerStateTo:1; - unsigned int delegateVideoDidPlayToEnd:1; - unsigned int delegateDidTapVideoNode:1; - unsigned int delegateVideoNodeWillChangePlayerStateToState:1; - unsigned int delegateVideoNodeDidPlayToTimeInterval:1; - unsigned int delegateVideoNodeDidStartInitialLoading:1; - unsigned int delegateVideoNodeDidFinishInitialLoading:1; - unsigned int delegateVideoNodeDidSetCurrentItem:1; - unsigned int delegateVideoNodeDidStallAtTimeInterval:1; - unsigned int delegateVideoNodeDidRecoverFromStall:1; - unsigned int delegateVideoNodeDidFailToLoadValueForKey:1; - } _delegateFlags; - - BOOL _shouldBePlaying; - - BOOL _shouldAutorepeat; - BOOL _shouldAutoplay; - BOOL _shouldAggressivelyRecoverFromStall; - BOOL _muted; - - ASVideoNodePlayerState _playerState; - - AVAsset *_asset; - NSURL *_assetURL; - AVVideoComposition *_videoComposition; - AVAudioMix *_audioMix; - - AVPlayerItem *_currentPlayerItem; - AVPlayer *_player; - - id _timeObserver; - int32_t _periodicTimeObserverTimescale; - CMTime _timeObserverInterval; - - CMTime _lastPlaybackTime; - - ASDisplayNode *_playerNode; - NSString *_gravity; -} - -@end - -@implementation ASVideoNode - -@dynamic delegate; - -// TODO: Support preview images with HTTP Live Streaming videos. - -#pragma mark - Construction and Layout - -- (instancetype)initWithCache:(id)cache downloader:(id)downloader -{ - if (!(self = [super initWithCache:cache downloader:downloader])) { - return nil; - } - - self.gravity = AVLayerVideoGravityResizeAspect; - _periodicTimeObserverTimescale = 10000; - [self addTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside]; - _lastPlaybackTime = kCMTimeZero; - - NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; - [notificationCenter addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil]; - - return self; -} - -- (ASDisplayNode *)constructPlayerNode -{ - ASVideoNode * __weak weakSelf = self; - - return [[ASDisplayNode alloc] initWithLayerBlock:^CALayer *{ - AVPlayerLayer *playerLayer = [[AVPlayerLayer alloc] init]; - playerLayer.player = weakSelf.player; - playerLayer.videoGravity = weakSelf.gravity; - return playerLayer; - }]; -} - -- (AVPlayerItem *)constructPlayerItem -{ - ASDisplayNodeAssertMainThread(); - ASLockScopeSelf(); - - AVPlayerItem *playerItem = nil; - if (_assetURL != nil) { - playerItem = [[AVPlayerItem alloc] initWithURL:_assetURL]; - _asset = [playerItem asset]; - } else { - playerItem = [[AVPlayerItem alloc] initWithAsset:_asset]; - } - - playerItem.videoComposition = _videoComposition; - playerItem.audioMix = _audioMix; - return playerItem; -} - -- (void)prepareToPlayAsset:(AVAsset *)asset withKeys:(NSArray *)requestedKeys -{ - ASDisplayNodeAssertMainThread(); - - for (NSString *key in requestedKeys) { - NSError *error = nil; - AVKeyValueStatus keyStatus = [asset statusOfValueForKey:key error:&error]; - if (keyStatus == AVKeyValueStatusFailed) { - NSLog(@"Asset loading failed with error: %@", error); - if (_delegateFlags.delegateVideoNodeDidFailToLoadValueForKey) { - [self.delegate videoNode:self didFailToLoadValueForKey:key asset:asset error:error]; - } - } - } - - if ([asset isPlayable] == NO) { - NSLog(@"Asset is not playable."); - return; - } - - AVPlayerItem *playerItem = [self constructPlayerItem]; - [self setCurrentItem:playerItem]; - - if (_player != nil) { - [_player replaceCurrentItemWithPlayerItem:playerItem]; - } else { - self.player = [AVPlayer playerWithPlayerItem:playerItem]; - } - - if (_delegateFlags.delegateVideoNodeDidSetCurrentItem) { - [self.delegate videoNode:self didSetCurrentItem:playerItem]; - } - - if (self.image == nil && self.URL == nil) { - [self generatePlaceholderImage]; - } -} - -- (void)addPlayerItemObservers:(AVPlayerItem *)playerItem -{ - if (playerItem == nil) { - return; - } - - [playerItem addObserver:self forKeyPath:kStatus options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:ASVideoNodeContext]; - [playerItem addObserver:self forKeyPath:kPlaybackLikelyToKeepUpKey options:NSKeyValueObservingOptionNew context:ASVideoNodeContext]; - [playerItem addObserver:self forKeyPath:kplaybackBufferEmpty options:NSKeyValueObservingOptionNew context:ASVideoNodeContext]; - - NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; - [notificationCenter addObserver:self selector:@selector(didPlayToEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem]; - [notificationCenter addObserver:self selector:@selector(videoNodeDidStall:) name:AVPlayerItemPlaybackStalledNotification object:playerItem]; - [notificationCenter addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemFailedToPlayToEndTimeNotification object:playerItem]; - [notificationCenter addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemNewErrorLogEntryNotification object:playerItem]; -} - -- (void)removePlayerItemObservers:(AVPlayerItem *)playerItem -{ - @try { - [playerItem removeObserver:self forKeyPath:kStatus context:ASVideoNodeContext]; - [playerItem removeObserver:self forKeyPath:kPlaybackLikelyToKeepUpKey context:ASVideoNodeContext]; - [playerItem removeObserver:self forKeyPath:kplaybackBufferEmpty context:ASVideoNodeContext]; - } - @catch (NSException * __unused exception) { - NSLog(@"Unnecessary KVO removal"); - } - - NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; - [notificationCenter removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem]; - [notificationCenter removeObserver:self name: AVPlayerItemPlaybackStalledNotification object:playerItem]; - [notificationCenter removeObserver:self name:AVPlayerItemFailedToPlayToEndTimeNotification object:playerItem]; - [notificationCenter removeObserver:self name:AVPlayerItemNewErrorLogEntryNotification object:playerItem]; -} - -- (void)addPlayerObservers:(AVPlayer *)player -{ - if (player == nil) { - return; - } - - [player addObserver:self forKeyPath:kRate options:NSKeyValueObservingOptionNew context:ASVideoNodeContext]; - - __weak __typeof(self) weakSelf = self; - _timeObserverInterval = CMTimeMake(1, _periodicTimeObserverTimescale); - _timeObserver = [player addPeriodicTimeObserverForInterval:_timeObserverInterval queue:NULL usingBlock:^(CMTime time){ - [weakSelf periodicTimeObserver:time]; - }]; -} - -- (void) removePlayerObservers:(AVPlayer *)player -{ - if (_timeObserver != nil) { - [player removeTimeObserver:_timeObserver]; - _timeObserver = nil; - } - - @try { - [player removeObserver:self forKeyPath:kRate context:ASVideoNodeContext]; - } - @catch (NSException * __unused exception) { - NSLog(@"Unnecessary KVO removal"); - } -} - -- (void)layout -{ - [super layout]; - // The _playerNode wraps AVPlayerLayer, and therefore should extend across the entire bounds. - _playerNode.frame = self.bounds; -} - -- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize -{ - ASDisplayNode *playerNode = ASLockedSelf(_playerNode); - - CGSize calculatedSize = constrainedSize; - - // Prevent crashes through if infinite width or height - if (isinf(calculatedSize.width) || isinf(calculatedSize.height)) { - ASDisplayNodeAssert(NO, @"Infinite width or height in ASVideoNode"); - calculatedSize = CGSizeZero; - } - - if (playerNode != nil) { - playerNode.style.preferredSize = calculatedSize; - [playerNode layoutThatFits:ASSizeRangeMake(CGSizeZero, calculatedSize)]; - } - - return calculatedSize; -} - -- (void)generatePlaceholderImage -{ - ASVideoNode * __weak weakSelf = self; - AVAsset *asset = self.asset; - - [self imageAtTime:kCMTimeZero completionHandler:^(UIImage *image) { - ASPerformBlockOnMainThread(^{ - // Ensure the asset hasn't changed since the image request was made - if (ASAssetIsEqual(weakSelf.asset, asset)) { - [weakSelf setVideoPlaceholderImage:image]; - } - }); - }]; -} - -- (void)imageAtTime:(CMTime)imageTime completionHandler:(void(^)(UIImage *image))completionHandler -{ - ASPerformBlockOnBackgroundThread(^{ - AVAsset *asset = self.asset; - - // Skip the asset image generation if we don't have any tracks available that are capable of supporting it - NSArray* visualAssetArray = [asset tracksWithMediaCharacteristic:AVMediaCharacteristicVisual]; - if (visualAssetArray.count == 0) { - completionHandler(nil); - return; - } - - AVAssetImageGenerator *previewImageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset]; - previewImageGenerator.appliesPreferredTrackTransform = YES; - previewImageGenerator.videoComposition = _videoComposition; - - [previewImageGenerator generateCGImagesAsynchronouslyForTimes:@[[NSValue valueWithCMTime:imageTime]] - completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error) { - if (error != nil && result != AVAssetImageGeneratorCancelled) { - NSLog(@"Asset preview image generation failed with error: %@", error); - } - completionHandler(image ? [UIImage imageWithCGImage:image] : nil); - }]; - }); -} - -- (void)setVideoPlaceholderImage:(UIImage *)image -{ - NSString *gravity = self.gravity; - - if (image != nil) { - self.contentMode = ASContentModeFromVideoGravity(gravity); - } - self.image = image; -} - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context -{ - AS::UniqueLock l(__instanceLock__); - - if (object == _currentPlayerItem) { - if ([keyPath isEqualToString:kStatus]) { - if ([change[NSKeyValueChangeNewKey] integerValue] == AVPlayerItemStatusReadyToPlay) { - if (self.playerState != ASVideoNodePlayerStatePlaying) { - self.playerState = ASVideoNodePlayerStateReadyToPlay; - if (_shouldBePlaying && ASInterfaceStateIncludesVisible(self.interfaceState)) { - l.unlock(); - [self play]; - l.lock(); - } - } - // If we don't yet have a placeholder image update it now that we should have data available for it - if (self.image == nil && self.URL == nil) { - [self generatePlaceholderImage]; - } - } - } else if ([keyPath isEqualToString:kPlaybackLikelyToKeepUpKey]) { - BOOL likelyToKeepUp = [change[NSKeyValueChangeNewKey] boolValue]; - if (likelyToKeepUp && self.playerState == ASVideoNodePlayerStatePlaying) { - return; - } - if (!likelyToKeepUp) { - self.playerState = ASVideoNodePlayerStateLoading; - } else if (self.playerState != ASVideoNodePlayerStateFinished) { - self.playerState = ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying; - } - if (_shouldBePlaying && (_shouldAggressivelyRecoverFromStall || likelyToKeepUp) && ASInterfaceStateIncludesVisible(self.interfaceState)) { - if (self.playerState == ASVideoNodePlayerStateLoading && _delegateFlags.delegateVideoNodeDidRecoverFromStall) { - [self.delegate videoNodeDidRecoverFromStall:self]; - } - - l.unlock(); - [self play]; // autoresume after buffer catches up - // NOTE: Early return without re-locking. - return; - } - } else if ([keyPath isEqualToString:kplaybackBufferEmpty]) { - if (_shouldBePlaying && [change[NSKeyValueChangeNewKey] boolValue] == YES && ASInterfaceStateIncludesVisible(self.interfaceState)) { - self.playerState = ASVideoNodePlayerStateLoading; - } - } - } else if (object == _player) { - if ([keyPath isEqualToString:kRate]) { - if ([change[NSKeyValueChangeNewKey] floatValue] == 0.0) { - if (self.playerState == ASVideoNodePlayerStatePlaying) { - self.playerState = ASVideoNodePlayerStatePaused; - } - } else { - self.playerState = ASVideoNodePlayerStatePlaying; - } - } - } - - // NOTE: Early return above. -} - -- (void)tapped -{ - if (_delegateFlags.delegateDidTapVideoNode) { - [self.delegate didTapVideoNode:self]; - - } else { - if (_shouldBePlaying) { - [self pause]; - } else { - [self play]; - } - } -} - -- (void)didEnterPreloadState -{ - [super didEnterPreloadState]; - - ASLockScopeSelf(); - AVAsset *asset = self.asset; - // Return immediately if the asset is nil; - if (asset == nil || self.playerState != ASVideoNodePlayerStateUnknown) { - return; - } - - self.playerState = ASVideoNodePlayerStateLoading; - if (_delegateFlags.delegateVideoNodeDidStartInitialLoading) { - [self.delegate videoNodeDidStartInitialLoading:self]; - } - - NSArray *requestedKeys = @[@"playable"]; - [asset loadValuesAsynchronouslyForKeys:requestedKeys completionHandler:^{ - ASPerformBlockOnMainThread(^{ - if (_delegateFlags.delegateVideoNodeDidFinishInitialLoading) { - [self.delegate videoNodeDidFinishInitialLoading:self]; - } - [self prepareToPlayAsset:asset withKeys:requestedKeys]; - }); - }]; -} - -- (void)periodicTimeObserver:(CMTime)time -{ - NSTimeInterval timeInSeconds = CMTimeGetSeconds(time); - if (timeInSeconds <= 0) { - return; - } - - if (_delegateFlags.delegateVideoNodeDidPlayToTimeInterval) { - [self.delegate videoNode:self didPlayToTimeInterval:timeInSeconds]; - - } -} - -- (void)didExitPreloadState -{ - [super didExitPreloadState]; - - { - ASLockScopeSelf(); - - self.player = nil; - self.currentItem = nil; - self.playerState = ASVideoNodePlayerStateUnknown; - } -} - -- (void)didEnterVisibleState -{ - [super didEnterVisibleState]; - - BOOL shouldPlay = NO; - { - ASLockScopeSelf(); - if (_shouldBePlaying || _shouldAutoplay) { - if (_player != nil && CMTIME_IS_VALID(_lastPlaybackTime)) { - [_player seekToTime:_lastPlaybackTime]; - } - shouldPlay = YES; - } - } - - if (shouldPlay) { - [self play]; - } -} - -- (void)didExitVisibleState -{ - [super didExitVisibleState]; - - ASLockScopeSelf(); - - if (_shouldBePlaying) { - [self pause]; - if (_player != nil && CMTIME_IS_VALID(_player.currentTime)) { - _lastPlaybackTime = _player.currentTime; - } - _shouldBePlaying = YES; - } -} - -#pragma mark - Video Properties - -- (void)setPlayerState:(ASVideoNodePlayerState)playerState -{ - ASLockScopeSelf(); - - ASVideoNodePlayerState oldState = _playerState; - - if (oldState == playerState) { - return; - } - - if (_delegateFlags.delegateVideoNodeWillChangePlayerStateToState) { - [self.delegate videoNode:self willChangePlayerState:oldState toState:playerState]; - } - - _playerState = playerState; -} - -- (void)setAssetURL:(NSURL *)assetURL -{ - ASDisplayNodeAssertMainThread(); - - if (ASObjectIsEqual(assetURL, self.assetURL) == NO) { - [self setAndFetchAsset:[AVURLAsset assetWithURL:assetURL] url:assetURL]; - } -} - -- (NSURL *)assetURL -{ - ASLockScopeSelf(); - - if (_assetURL != nil) { - return _assetURL; - } else if ([_asset isKindOfClass:AVURLAsset.class]) { - return ((AVURLAsset *)_asset).URL; - } - - return nil; -} - -- (void)setAsset:(AVAsset *)asset -{ - ASDisplayNodeAssertMainThread(); - - if (ASAssetIsEqual(asset, self.asset) == NO) { - [self setAndFetchAsset:asset url:nil]; - } -} - -- (AVAsset *)asset -{ - ASLockScopeSelf(); - return _asset; -} - -- (void)setAndFetchAsset:(AVAsset *)asset url:(NSURL *)assetURL -{ - ASDisplayNodeAssertMainThread(); - - [self didExitPreloadState]; - - { - ASLockScopeSelf(); - self.videoPlaceholderImage = nil; - _asset = asset; - _assetURL = assetURL; - } - - [self setNeedsPreload]; -} - -- (void)setVideoComposition:(AVVideoComposition *)videoComposition -{ - ASLockScopeSelf(); - - _videoComposition = videoComposition; - _currentPlayerItem.videoComposition = videoComposition; -} - -- (AVVideoComposition *)videoComposition -{ - ASLockScopeSelf(); - return _videoComposition; -} - -- (void)setAudioMix:(AVAudioMix *)audioMix -{ - ASLockScopeSelf(); - - _audioMix = audioMix; - _currentPlayerItem.audioMix = audioMix; -} - -- (AVAudioMix *)audioMix -{ - ASLockScopeSelf(); - return _audioMix; -} - -- (AVPlayer *)player -{ - ASLockScopeSelf(); - return _player; -} - -- (AVPlayerLayer *)playerLayer -{ - ASLockScopeSelf(); - return (AVPlayerLayer *)_playerNode.layer; -} - -- (void)setDelegate:(id)delegate -{ - [super setDelegate:delegate]; - - if (delegate == nil) { - memset(&_delegateFlags, 0, sizeof(_delegateFlags)); - } else { - _delegateFlags.delegateVideNodeShouldChangePlayerStateTo = [delegate respondsToSelector:@selector(videoNode:shouldChangePlayerStateTo:)]; - _delegateFlags.delegateVideoDidPlayToEnd = [delegate respondsToSelector:@selector(videoDidPlayToEnd:)]; - _delegateFlags.delegateDidTapVideoNode = [delegate respondsToSelector:@selector(didTapVideoNode:)]; - _delegateFlags.delegateVideoNodeWillChangePlayerStateToState = [delegate respondsToSelector:@selector(videoNode:willChangePlayerState:toState:)]; - _delegateFlags.delegateVideoNodeDidPlayToTimeInterval = [delegate respondsToSelector:@selector(videoNode:didPlayToTimeInterval:)]; - _delegateFlags.delegateVideoNodeDidStartInitialLoading = [delegate respondsToSelector:@selector(videoNodeDidStartInitialLoading:)]; - _delegateFlags.delegateVideoNodeDidFinishInitialLoading = [delegate respondsToSelector:@selector(videoNodeDidFinishInitialLoading:)]; - _delegateFlags.delegateVideoNodeDidSetCurrentItem = [delegate respondsToSelector:@selector(videoNode:didSetCurrentItem:)]; - _delegateFlags.delegateVideoNodeDidStallAtTimeInterval = [delegate respondsToSelector:@selector(videoNode:didStallAtTimeInterval:)]; - _delegateFlags.delegateVideoNodeDidRecoverFromStall = [delegate respondsToSelector:@selector(videoNodeDidRecoverFromStall:)]; - _delegateFlags.delegateVideoNodeDidFailToLoadValueForKey = [delegate respondsToSelector:@selector(videoNode:didFailToLoadValueForKey:asset:error:)]; - } -} - -- (void)setGravity:(NSString *)gravity -{ - ASLockScopeSelf(); - if (!gravity) { - gravity = AVLayerVideoGravityResizeAspect; - } - - if (!ASCompareAssignObjects(_gravity, gravity)) { - return; - } - - if (_playerNode.isNodeLoaded) { - ((AVPlayerLayer *)_playerNode.layer).videoGravity = gravity; - } - self.contentMode = ASContentModeFromVideoGravity(gravity); - _gravity = gravity; -} - -- (NSString *)gravity -{ - ASLockScopeSelf(); - return _gravity; -} - -- (BOOL)muted -{ - ASLockScopeSelf(); - return _muted; -} - -- (void)setMuted:(BOOL)muted -{ - ASLockScopeSelf(); - - _player.muted = muted; - _muted = muted; -} - -#pragma mark - Video Playback - -- (void)play -{ - ASLockScopeSelf(); - - if (![self isStateChangeValid:ASVideoNodePlayerStatePlaying]) { - return; - } - - if (_player == nil) { - ASUnlockScope(self); - [self setNeedsPreload]; - } - - if (_playerNode == nil) { - _playerNode = [self constructPlayerNode]; - - { - ASUnlockScope(self); - [self addSubnode:_playerNode]; - } - - [self setNeedsLayout]; - } - - - [_player play]; - _shouldBePlaying = YES; -} - -- (BOOL)ready -{ - return _currentPlayerItem.status == AVPlayerItemStatusReadyToPlay; -} - -- (void)pause -{ - ASLockScopeSelf(); - if (![self isStateChangeValid:ASVideoNodePlayerStatePaused]) { - return; - } - [_player pause]; - _shouldBePlaying = NO; -} - -- (BOOL)isPlaying -{ - ASLockScopeSelf(); - - return (_player.rate > 0 && !_player.error); -} - -- (BOOL)isStateChangeValid:(ASVideoNodePlayerState)state -{ - if (_delegateFlags.delegateVideNodeShouldChangePlayerStateTo) { - if (![self.delegate videoNode:self shouldChangePlayerStateTo:state]) { - return NO; - } - } - return YES; -} - -- (void)resetToPlaceholder -{ - ASLockScopeSelf(); - - if (_playerNode != nil) { - [_playerNode removeFromSupernode]; - _playerNode = nil; - } - - [_player seekToTime:kCMTimeZero]; - [self pause]; -} - - -#pragma mark - Playback observers - -- (void)applicationDidBecomeActive:(NSNotification *)notification -{ - if (self.shouldBePlaying && self.isVisible) { - [self play]; - } -} - -- (void)didPlayToEnd:(NSNotification *)notification -{ - self.playerState = ASVideoNodePlayerStateFinished; - if (_delegateFlags.delegateVideoDidPlayToEnd) { - [self.delegate videoDidPlayToEnd:self]; - } - - if (_shouldAutorepeat) { - [_player seekToTime:kCMTimeZero]; - [self play]; - } else { - [self pause]; - } -} - -- (void)videoNodeDidStall:(NSNotification *)notification -{ - self.playerState = ASVideoNodePlayerStateLoading; - if (_delegateFlags.delegateVideoNodeDidStallAtTimeInterval) { - [self.delegate videoNode:self didStallAtTimeInterval:CMTimeGetSeconds(_player.currentItem.currentTime)]; - } -} - -- (void)errorWhilePlaying:(NSNotification *)notification -{ - if ([notification.name isEqualToString:AVPlayerItemFailedToPlayToEndTimeNotification]) { - NSLog(@"Failed to play video"); - } else if ([notification.name isEqualToString:AVPlayerItemNewErrorLogEntryNotification]) { - AVPlayerItem *item = (AVPlayerItem *)notification.object; - AVPlayerItemErrorLogEvent *logEvent = item.errorLog.events.lastObject; - NSLog(@"AVPlayerItem error log entry added for video with error %@ status %@", item.error, - (item.status == AVPlayerItemStatusFailed ? @"FAILED" : [NSString stringWithFormat:@"%ld", (long)item.status])); - NSLog(@"Item is %@", item); - - if (logEvent) { - NSLog(@"Log code %ld domain %@ comment %@", (long)logEvent.errorStatusCode, logEvent.errorDomain, logEvent.errorComment); - } - } -} - -#pragma mark - Internal Properties - -- (AVPlayerItem *)currentItem -{ - ASLockScopeSelf(); - return _currentPlayerItem; -} - -- (void)setCurrentItem:(AVPlayerItem *)currentItem -{ - ASLockScopeSelf(); - - [self removePlayerItemObservers:_currentPlayerItem]; - - _currentPlayerItem = currentItem; - - if (currentItem != nil) { - [self addPlayerItemObservers:currentItem]; - } -} - -- (ASDisplayNode *)playerNode -{ - ASLockScopeSelf(); - return _playerNode; -} - -- (void)setPlayerNode:(ASDisplayNode *)playerNode -{ - { - ASLockScopeSelf(); - _playerNode = playerNode; - } - - [self setNeedsLayout]; -} - -- (void)setPlayer:(AVPlayer *)player -{ - ASLockScopeSelf(); - - [self removePlayerObservers:_player]; - - _player = player; - player.muted = _muted; - ((AVPlayerLayer *)_playerNode.layer).player = player; - - if (player != nil) { - [self addPlayerObservers:player]; - } -} - -- (BOOL)shouldBePlaying -{ - ASLockScopeSelf(); - return _shouldBePlaying; -} - -- (void)setShouldBePlaying:(BOOL)shouldBePlaying -{ - ASLockScopeSelf(); - _shouldBePlaying = shouldBePlaying; -} - -#pragma mark - Lifecycle - -- (void)dealloc -{ - [self removePlayerItemObservers:_currentPlayerItem]; - [self removePlayerObservers:_player]; - - NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; - [notificationCenter removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; -} - -@end -#endif -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASVideoPlayerNode.h b/submodules/AsyncDisplayKit/Source/ASVideoPlayerNode.h deleted file mode 100644 index 879628051a..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASVideoPlayerNode.h +++ /dev/null @@ -1,228 +0,0 @@ -// -// ASVideoPlayerNode.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -#if AS_USE_VIDEO - -#if TARGET_OS_IOS -#import -#import -#import -#import - -@class AVAsset; -@class ASButtonNode; -@protocol ASVideoPlayerNodeDelegate; - -typedef NS_ENUM(NSInteger, ASVideoPlayerNodeControlType) { - ASVideoPlayerNodeControlTypePlaybackButton, - ASVideoPlayerNodeControlTypeElapsedText, - ASVideoPlayerNodeControlTypeDurationText, - ASVideoPlayerNodeControlTypeScrubber, - ASVideoPlayerNodeControlTypeFullScreenButton, - ASVideoPlayerNodeControlTypeFlexGrowSpacer, -}; - -NS_ASSUME_NONNULL_BEGIN - -@interface ASVideoPlayerNode : ASDisplayNode - -@property (nullable, nonatomic, weak) id delegate; - -@property (nonatomic, readonly) CMTime duration; - -@property (nonatomic) BOOL controlsDisabled; - -#pragma mark - ASVideoNode property proxy -/** - * When shouldAutoplay is set to true, a video node will play when it has both loaded and entered the "visible" interfaceState. - * If it leaves the visible interfaceState it will pause but will resume once it has returned. - */ -@property (nonatomic) BOOL shouldAutoPlay; -@property (nonatomic) BOOL shouldAutoRepeat; -@property (nonatomic) BOOL muted; -@property (nonatomic, readonly) ASVideoNodePlayerState playerState; -@property (nonatomic) BOOL shouldAggressivelyRecoverFromStall; -@property (nullable, nonatomic) NSURL *placeholderImageURL; - -@property (nullable, nonatomic) AVAsset *asset; -/** - ** @abstract The URL with which the asset was initialized. - ** @discussion Setting the URL will override the current asset with a newly created AVURLAsset created from the given URL, and AVAsset *asset will point to that newly created AVURLAsset. Please don't set both assetURL and asset. - ** @return Current URL the asset was initialized or nil if no URL was given. - **/ -@property (nullable, nonatomic) NSURL *assetURL; - -/// You should never set any value on the backing video node. Use exclusivively the video player node to set properties -@property (nonatomic, readonly) ASVideoNode *videoNode; - -//! Defaults to 100 -@property (nonatomic) int32_t periodicTimeObserverTimescale; -//! Defaults to AVLayerVideoGravityResizeAspect -@property (nonatomic, copy) NSString *gravity; - -#pragma mark - Lifecycle -- (instancetype)initWithURL:(NSURL *)URL; -- (instancetype)initWithAsset:(AVAsset *)asset; -- (instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix; - -#pragma mark - Public API -- (void)seekToTime:(CGFloat)percentComplete; -- (void)play; -- (void)pause; -- (BOOL)isPlaying; -- (void)resetToPlaceholder; - -@end - -#pragma mark - ASVideoPlayerNodeDelegate - -@protocol ASVideoPlayerNodeDelegate -@optional -/** - * @abstract Delegate method invoked before creating controlbar controls - * @param videoPlayer The sender - */ -- (NSArray *)videoPlayerNodeNeededDefaultControls:(ASVideoPlayerNode*)videoPlayer; - -/** - * @abstract Delegate method invoked before creating default controls, asks delegate for custom controls dictionary. - * This dictionary must constain only ASDisplayNode subclass objects. - * @param videoPlayer The sender - * @discussion - This method is invoked only when developer implements videoPlayerNodeLayoutSpec:forControls:forMaximumSize: - * and gives ability to add custom constrols to ASVideoPlayerNode, for example mute button. - */ -- (NSDictionary *)videoPlayerNodeCustomControls:(ASVideoPlayerNode*)videoPlayer; - -/** - * @abstract Delegate method invoked in layoutSpecThatFits: - * @param videoPlayer The sender - * @param controls - Dictionary of controls which are used in videoPlayer; Dictionary keys are ASVideoPlayerNodeControlType - * @param maxSize - Maximum size for ASVideoPlayerNode - * @discussion - Developer can layout whole ASVideoPlayerNode as he wants. ASVideoNode is locked and it can't be changed - */ -- (ASLayoutSpec *)videoPlayerNodeLayoutSpec:(ASVideoPlayerNode *)videoPlayer - forControls:(NSDictionary *)controls - forMaximumSize:(CGSize)maxSize; - -#pragma mark Text delegate methods -/** - * @abstract Delegate method invoked before creating ASVideoPlayerNodeControlTypeElapsedText and ASVideoPlayerNodeControlTypeDurationText - * @param videoPlayer The sender - * @param timeLabelType The of the time label - */ -- (NSDictionary *)videoPlayerNodeTimeLabelAttributes:(ASVideoPlayerNode *)videoPlayer timeLabelType:(ASVideoPlayerNodeControlType)timeLabelType; -- (NSString *)videoPlayerNode:(ASVideoPlayerNode *)videoPlayerNode - timeStringForTimeLabelType:(ASVideoPlayerNodeControlType)timeLabelType - forTime:(CMTime)time; - -#pragma mark Scrubber delegate methods -- (UIColor *)videoPlayerNodeScrubberMaximumTrackTint:(ASVideoPlayerNode *)videoPlayer; -- (UIColor *)videoPlayerNodeScrubberMinimumTrackTint:(ASVideoPlayerNode *)videoPlayer; -- (UIColor *)videoPlayerNodeScrubberThumbTint:(ASVideoPlayerNode *)videoPlayer; -- (UIImage *)videoPlayerNodeScrubberThumbImage:(ASVideoPlayerNode *)videoPlayer; - -#pragma mark - Spinner delegate methods -- (UIColor *)videoPlayerNodeSpinnerTint:(ASVideoPlayerNode *)videoPlayer; -- (UIActivityIndicatorViewStyle)videoPlayerNodeSpinnerStyle:(ASVideoPlayerNode *)videoPlayer; - -#pragma mark - Playback button delegate methods -- (UIColor *)videoPlayerNodePlaybackButtonTint:(ASVideoPlayerNode *)videoPlayer; - -#pragma mark - Fullscreen button delegate methods - -- (UIImage *)videoPlayerNodeFullScreenButtonImage:(ASVideoPlayerNode *)videoPlayer; - - -#pragma mark ASVideoNodeDelegate proxy methods -/** - * @abstract Delegate method invoked when ASVideoPlayerNode is taped. - * @param videoPlayer The ASVideoPlayerNode that was tapped. - */ -- (void)didTapVideoPlayerNode:(ASVideoPlayerNode *)videoPlayer; - -/** - * @abstract Delegate method invoked when fullcreen button is taped. - * @param buttonNode The fullscreen button node that was tapped. - */ -- (void)didTapFullScreenButtonNode:(ASButtonNode *)buttonNode; - -/** - * @abstract Delegate method invoked when ASVideoNode playback time is updated. - * @param videoPlayer The video player node - * @param time current playback time. - */ -- (void)videoPlayerNode:(ASVideoPlayerNode *)videoPlayer didPlayToTime:(CMTime)time; - -/** - * @abstract Delegate method invoked when ASVideoNode changes state. - * @param videoPlayer The ASVideoPlayerNode whose ASVideoNode is changing state. - * @param state ASVideoNode state before this change. - * @param toState ASVideoNode new state. - * @discussion This method is called after each state change - */ -- (void)videoPlayerNode:(ASVideoPlayerNode *)videoPlayer willChangeVideoNodeState:(ASVideoNodePlayerState)state toVideoNodeState:(ASVideoNodePlayerState)toState; - -/** - * @abstract Delegate method is invoked when ASVideoNode decides to change state. - * @param videoPlayer The ASVideoPlayerNode whose ASVideoNode is changing state. - * @param state ASVideoNode that is going to be set. - * @discussion Delegate method invoked when player changes it's state to - * ASVideoNodePlayerStatePlaying or ASVideoNodePlayerStatePaused - * and asks delegate if state change is valid - */ -- (BOOL)videoPlayerNode:(ASVideoPlayerNode*)videoPlayer shouldChangeVideoNodeStateTo:(ASVideoNodePlayerState)state; - -/** - * @abstract Delegate method invoked when the ASVideoNode has played to its end time. - * @param videoPlayer The video node has played to its end time. - */ -- (void)videoPlayerNodeDidPlayToEnd:(ASVideoPlayerNode *)videoPlayer; - -/** - * @abstract Delegate method invoked when the ASVideoNode has constructed its AVPlayerItem for the asset. - * @param videoPlayer The video player node. - * @param currentItem The AVPlayerItem that was constructed from the asset. - */ -- (void)videoPlayerNode:(ASVideoPlayerNode *)videoPlayer didSetCurrentItem:(AVPlayerItem *)currentItem; - -/** - * @abstract Delegate method invoked when the ASVideoNode stalls. - * @param videoPlayer The video player node that has experienced the stall - * @param timeInterval Current playback time when the stall happens - */ -- (void)videoPlayerNode:(ASVideoPlayerNode *)videoPlayer didStallAtTimeInterval:(NSTimeInterval)timeInterval; - -/** - * @abstract Delegate method invoked when the ASVideoNode starts the inital asset loading - * @param videoPlayer The videoPlayer - */ -- (void)videoPlayerNodeDidStartInitialLoading:(ASVideoPlayerNode *)videoPlayer; - -/** - * @abstract Delegate method invoked when the ASVideoNode is done loading the asset and can start the playback - * @param videoPlayer The videoPlayer - */ -- (void)videoPlayerNodeDidFinishInitialLoading:(ASVideoPlayerNode *)videoPlayer; - -/** - * @abstract Delegate method invoked when the ASVideoNode has recovered from the stall - * @param videoPlayer The videoplayer - */ -- (void)videoPlayerNodeDidRecoverFromStall:(ASVideoPlayerNode *)videoPlayer; - - -@end -NS_ASSUME_NONNULL_END -#endif - -#endif -#endif // TARGET_OS_IOS diff --git a/submodules/AsyncDisplayKit/Source/ASVideoPlayerNode.mm b/submodules/AsyncDisplayKit/Source/ASVideoPlayerNode.mm deleted file mode 100644 index eaa8cd91a9..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASVideoPlayerNode.mm +++ /dev/null @@ -1,1024 +0,0 @@ -// -// ASVideoPlayerNode.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#ifndef MINIMAL_ASDK - -#import - -#if AS_USE_VIDEO -#if TARGET_OS_IOS - -#import - -#import -#import -#import -#import -#import - -static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; - -@interface ASVideoPlayerNode() -{ - __weak id _delegate; - - struct { - unsigned int delegateNeededDefaultControls:1; - unsigned int delegateCustomControls:1; - unsigned int delegateSpinnerTintColor:1; - unsigned int delegateSpinnerStyle:1; - unsigned int delegatePlaybackButtonTint:1; - unsigned int delegateFullScreenButtonImage:1; - unsigned int delegateScrubberMaximumTrackTintColor:1; - unsigned int delegateScrubberMinimumTrackTintColor:1; - unsigned int delegateScrubberThumbTintColor:1; - unsigned int delegateScrubberThumbImage:1; - unsigned int delegateTimeLabelAttributes:1; - unsigned int delegateTimeLabelAttributedString:1; - unsigned int delegateLayoutSpecForControls:1; - unsigned int delegateVideoNodeDidPlayToTime:1; - unsigned int delegateVideoNodeWillChangeState:1; - unsigned int delegateVideoNodeShouldChangeState:1; - unsigned int delegateVideoNodePlaybackDidFinish:1; - unsigned int delegateDidTapVideoPlayerNode:1; - unsigned int delegateDidTapFullScreenButtonNode:1; - unsigned int delegateVideoPlayerNodeDidSetCurrentItem:1; - unsigned int delegateVideoPlayerNodeDidStallAtTimeInterval:1; - unsigned int delegateVideoPlayerNodeDidStartInitialLoading:1; - unsigned int delegateVideoPlayerNodeDidFinishInitialLoading:1; - unsigned int delegateVideoPlayerNodeDidRecoverFromStall:1; - } _delegateFlags; - - // The asset passed in the initializer will be assigned as pending asset. As soon as the first - // preload state happened all further asset handling is made by using the asset of the backing - // video node - AVAsset *_pendingAsset; - - // The backing video node. Ideally this is the source of truth and the video player node should - // not handle anything related to asset management - ASVideoNode *_videoNode; - - NSArray *_neededDefaultControls; - - NSMutableDictionary *_cachedControls; - - ASDefaultPlaybackButton *_playbackButtonNode; - ASButtonNode *_fullScreenButtonNode; - ASTextNode *_elapsedTextNode; - ASTextNode *_durationTextNode; - ASDisplayNode *_scrubberNode; - ASStackLayoutSpec *_controlFlexGrowSpacerSpec; - ASDisplayNode *_spinnerNode; - - BOOL _isSeeking; - CMTime _duration; - - BOOL _controlsDisabled; - - BOOL _shouldAutoPlay; - BOOL _shouldAutoRepeat; - BOOL _muted; - int32_t _periodicTimeObserverTimescale; - NSString *_gravity; - - BOOL _shouldAggressivelyRecoverFromStall; - - UIColor *_defaultControlsColor; -} - -@end - -@implementation ASVideoPlayerNode - -@dynamic placeholderImageURL; - -#pragma mark - Lifecycle - -- (instancetype)init -{ - if (!(self = [super init])) { - return nil; - } - - [self _initControlsAndVideoNode]; - - return self; -} - -- (instancetype)initWithAsset:(AVAsset *)asset -{ - if (!(self = [self init])) { - return nil; - } - - _pendingAsset = asset; - - return self; -} - -- (instancetype)initWithURL:(NSURL *)URL -{ - return [self initWithAsset:[AVAsset assetWithURL:URL]]; -} - -- (instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix -{ - if (!(self = [self initWithAsset:asset])) { - return nil; - } - - _videoNode.videoComposition = videoComposition; - _videoNode.audioMix = audioMix; - - return self; -} - -- (void)_initControlsAndVideoNode -{ - _defaultControlsColor = [UIColor whiteColor]; - _cachedControls = [[NSMutableDictionary alloc] init]; - - _videoNode = [[ASVideoNode alloc] init]; - _videoNode.delegate = self; - [self addSubnode:_videoNode]; -} - -#pragma mark - Setter / Getter - -- (void)setAssetURL:(NSURL *)assetURL -{ - ASDisplayNodeAssertMainThread(); - - self.asset = [AVAsset assetWithURL:assetURL]; -} - -- (NSURL *)assetURL -{ - NSURL *url = nil; - { - ASLockScopeSelf(); - if ([_pendingAsset isKindOfClass:AVURLAsset.class]) { - url = ((AVURLAsset *)_pendingAsset).URL; - } - } - - return url ?: _videoNode.assetURL; -} - -- (void)setAsset:(AVAsset *)asset -{ - ASDisplayNodeAssertMainThread(); - - [self lock]; - - // Clean out pending asset - _pendingAsset = nil; - - // Set asset based on interface state - if ((ASInterfaceStateIncludesPreload(self.interfaceState))) { - // Don't hold the lock while accessing the subnode - [self unlock]; - _videoNode.asset = asset; - return; - } - - _pendingAsset = asset; - [self unlock]; -} - -- (AVAsset *)asset -{ - return ASLockedSelf(_pendingAsset) ?: _videoNode.asset; -} - -#pragma mark - ASDisplayNode - -- (void)didLoad -{ - [super didLoad]; - - [self createControls]; -} - -- (void)didEnterPreloadState -{ - [super didEnterPreloadState]; - - AVAsset *pendingAsset = nil; - { - ASLockScopeSelf(); - pendingAsset = _pendingAsset; - _pendingAsset = nil; - } - - // If we enter preload state we apply the pending asset to load to the video node so it can start and fetch the asset - if (pendingAsset != nil && _videoNode.asset != pendingAsset) { - _videoNode.asset = pendingAsset; - } -} - -- (BOOL)supportsLayerBacking -{ - return NO; -} - -#pragma mark - UI - -- (void)createControls -{ - { - ASLockScopeSelf(); - - if (_controlsDisabled) { - return; - } - - if (_neededDefaultControls == nil) { - _neededDefaultControls = [self createDefaultControlElementArray]; - } - - if (_cachedControls == nil) { - _cachedControls = [[NSMutableDictionary alloc] init]; - } - - for (id object in _neededDefaultControls) { - ASVideoPlayerNodeControlType type = (ASVideoPlayerNodeControlType)[object integerValue]; - switch (type) { - case ASVideoPlayerNodeControlTypePlaybackButton: - [self _locked_createPlaybackButton]; - break; - case ASVideoPlayerNodeControlTypeElapsedText: - [self _locked_createElapsedTextField]; - break; - case ASVideoPlayerNodeControlTypeDurationText: - [self _locked_createDurationTextField]; - break; - case ASVideoPlayerNodeControlTypeScrubber: - [self _locked_createScrubber]; - break; - case ASVideoPlayerNodeControlTypeFullScreenButton: - [self _locked_createFullScreenButton]; - break; - case ASVideoPlayerNodeControlTypeFlexGrowSpacer: - [self _locked_createControlFlexGrowSpacer]; - break; - default: - break; - } - } - - if (_delegateFlags.delegateCustomControls && _delegateFlags.delegateLayoutSpecForControls) { - NSDictionary *customControls = [_delegate videoPlayerNodeCustomControls:self]; - std::vector subnodes; - for (id key in customControls) { - id node = customControls[key]; - if (![node isKindOfClass:[ASDisplayNode class]]) { - continue; - } - - subnodes.push_back(node); - [_cachedControls setObject:node forKey:key]; - } - - { - ASUnlockScope(self); - for (ASDisplayNode *subnode : subnodes) { - [self addSubnode:subnode]; - } - } - } - } - - ASPerformBlockOnMainThread(^{ - [self setNeedsLayout]; - }); -} - -- (NSArray *)createDefaultControlElementArray -{ - if (_delegateFlags.delegateNeededDefaultControls) { - return [_delegate videoPlayerNodeNeededDefaultControls:self]; - } - - return @[ @(ASVideoPlayerNodeControlTypePlaybackButton), - @(ASVideoPlayerNodeControlTypeElapsedText), - @(ASVideoPlayerNodeControlTypeScrubber), - @(ASVideoPlayerNodeControlTypeDurationText) ]; -} - -- (void)removeControls -{ - NSMutableDictionary *cachedControls = nil; - { - ASLockScope(self); - - // Grab the cached controls for removing it - cachedControls = [_cachedControls copy]; - [self _locked_cleanCachedControls]; - } - - for (ASDisplayNode *node in [cachedControls objectEnumerator]) { - [node removeFromSupernode]; - } -} - -- (void)_locked_cleanCachedControls -{ - [_cachedControls removeAllObjects]; - - _playbackButtonNode = nil; - _fullScreenButtonNode = nil; - _elapsedTextNode = nil; - _durationTextNode = nil; - _scrubberNode = nil; -} - -- (void)_locked_createPlaybackButton -{ - ASAssertLocked(__instanceLock__); - - if (_playbackButtonNode == nil) { - _playbackButtonNode = [[ASDefaultPlaybackButton alloc] init]; - _playbackButtonNode.style.preferredSize = CGSizeMake(16.0, 22.0); - - if (_delegateFlags.delegatePlaybackButtonTint) { - _playbackButtonNode.tintColor = [_delegate videoPlayerNodePlaybackButtonTint:self]; - } else { - _playbackButtonNode.tintColor = _defaultControlsColor; - } - - if (_videoNode.playerState == ASVideoNodePlayerStatePlaying) { - _playbackButtonNode.buttonType = ASDefaultPlaybackButtonTypePause; - } - - [_playbackButtonNode addTarget:self action:@selector(didTapPlaybackButton:) forControlEvents:ASControlNodeEventTouchUpInside]; - [_cachedControls setObject:_playbackButtonNode forKey:@(ASVideoPlayerNodeControlTypePlaybackButton)]; - } - - { - ASUnlockScope(self); - [self addSubnode:_playbackButtonNode]; - } -} - -- (void)_locked_createFullScreenButton -{ - ASAssertLocked(__instanceLock__); - - if (_fullScreenButtonNode == nil) { - _fullScreenButtonNode = [[ASButtonNode alloc] init]; - _fullScreenButtonNode.style.preferredSize = CGSizeMake(16.0, 22.0); - - if (_delegateFlags.delegateFullScreenButtonImage) { - [_fullScreenButtonNode setImage:[_delegate videoPlayerNodeFullScreenButtonImage:self] forState:UIControlStateNormal]; - } - - [_fullScreenButtonNode addTarget:self action:@selector(didTapFullScreenButton:) forControlEvents:ASControlNodeEventTouchUpInside]; - [_cachedControls setObject:_fullScreenButtonNode forKey:@(ASVideoPlayerNodeControlTypeFullScreenButton)]; - } - - { - ASUnlockScope(self); - [self addSubnode:_fullScreenButtonNode]; - } -} - -- (void)_locked_createElapsedTextField -{ - ASAssertLocked(__instanceLock__); - - if (_elapsedTextNode == nil) { - _elapsedTextNode = [[ASTextNode alloc] init]; - _elapsedTextNode.attributedText = [self timeLabelAttributedStringForString:@"00:00" - forControlType:ASVideoPlayerNodeControlTypeElapsedText]; - _elapsedTextNode.truncationMode = NSLineBreakByClipping; - - [_cachedControls setObject:_elapsedTextNode forKey:@(ASVideoPlayerNodeControlTypeElapsedText)]; - } - { - ASUnlockScope(self); - [self addSubnode:_elapsedTextNode]; - } -} - -- (void)_locked_createDurationTextField -{ - ASAssertLocked(__instanceLock__); - - if (_durationTextNode == nil) { - _durationTextNode = [[ASTextNode alloc] init]; - _durationTextNode.attributedText = [self timeLabelAttributedStringForString:@"00:00" - forControlType:ASVideoPlayerNodeControlTypeDurationText]; - _durationTextNode.truncationMode = NSLineBreakByClipping; - - [_cachedControls setObject:_durationTextNode forKey:@(ASVideoPlayerNodeControlTypeDurationText)]; - } - [self updateDurationTimeLabel]; - { - ASUnlockScope(self); - [self addSubnode:_durationTextNode]; - } -} - -- (void)_locked_createScrubber -{ - ASAssertLocked(__instanceLock__); - - if (_scrubberNode == nil) { - __weak __typeof__(self) weakSelf = self; - _scrubberNode = [[ASDisplayNode alloc] initWithViewBlock:^UIView * _Nonnull { - __typeof__(self) strongSelf = weakSelf; - - UISlider *slider = [[UISlider alloc] initWithFrame:CGRectZero]; - slider.minimumValue = 0.0; - slider.maximumValue = 1.0; - - if (_delegateFlags.delegateScrubberMinimumTrackTintColor) { - slider.minimumTrackTintColor = [strongSelf.delegate videoPlayerNodeScrubberMinimumTrackTint:strongSelf]; - } - - if (_delegateFlags.delegateScrubberMaximumTrackTintColor) { - slider.maximumTrackTintColor = [strongSelf.delegate videoPlayerNodeScrubberMaximumTrackTint:strongSelf]; - } - - if (_delegateFlags.delegateScrubberThumbTintColor) { - slider.thumbTintColor = [strongSelf.delegate videoPlayerNodeScrubberThumbTint:strongSelf]; - } - - if (_delegateFlags.delegateScrubberThumbImage) { - UIImage *thumbImage = [strongSelf.delegate videoPlayerNodeScrubberThumbImage:strongSelf]; - [slider setThumbImage:thumbImage forState:UIControlStateNormal]; - } - - - [slider addTarget:strongSelf action:@selector(beginSeek) forControlEvents:UIControlEventTouchDown]; - [slider addTarget:strongSelf action:@selector(endSeek) forControlEvents:UIControlEventTouchUpInside|UIControlEventTouchUpOutside|UIControlEventTouchCancel]; - [slider addTarget:strongSelf action:@selector(seekTimeDidChange:) forControlEvents:UIControlEventValueChanged]; - - return slider; - }]; - - _scrubberNode.style.flexShrink = 1; - - [_cachedControls setObject:_scrubberNode forKey:@(ASVideoPlayerNodeControlTypeScrubber)]; - } - { - ASUnlockScope(self); - [self addSubnode:_scrubberNode]; - } -} - -- (void)_locked_createControlFlexGrowSpacer -{ - ASAssertLocked(__instanceLock__); - - if (_controlFlexGrowSpacerSpec == nil) { - _controlFlexGrowSpacerSpec = [[ASStackLayoutSpec alloc] init]; - _controlFlexGrowSpacerSpec.style.flexGrow = 1.0; - } - - [_cachedControls setObject:_controlFlexGrowSpacerSpec forKey:@(ASVideoPlayerNodeControlTypeFlexGrowSpacer)]; -} - -- (void)updateDurationTimeLabel -{ - if (!_durationTextNode) { - return; - } - NSString *formattedDuration = [self timeStringForCMTime:_duration forTimeLabelType:ASVideoPlayerNodeControlTypeDurationText]; - _durationTextNode.attributedText = [self timeLabelAttributedStringForString:formattedDuration forControlType:ASVideoPlayerNodeControlTypeDurationText]; -} - -- (void)updateElapsedTimeLabel:(NSTimeInterval)seconds -{ - if (!_elapsedTextNode) { - return; - } - NSString *formattedElapsed = [self timeStringForCMTime:CMTimeMakeWithSeconds( seconds, _videoNode.periodicTimeObserverTimescale ) forTimeLabelType:ASVideoPlayerNodeControlTypeElapsedText]; - _elapsedTextNode.attributedText = [self timeLabelAttributedStringForString:formattedElapsed forControlType:ASVideoPlayerNodeControlTypeElapsedText]; -} - -- (NSAttributedString*)timeLabelAttributedStringForString:(NSString*)string forControlType:(ASVideoPlayerNodeControlType)controlType -{ - NSDictionary *options; - if (_delegateFlags.delegateTimeLabelAttributes) { - options = [_delegate videoPlayerNodeTimeLabelAttributes:self timeLabelType:controlType]; - } else { - options = @{ - NSFontAttributeName : [UIFont systemFontOfSize:12.0], - NSForegroundColorAttributeName: _defaultControlsColor - }; - } - - - NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:string attributes:options]; - - return attributedString; -} - -#pragma mark - ASVideoNodeDelegate -- (void)videoNode:(ASVideoNode *)videoNode willChangePlayerState:(ASVideoNodePlayerState)state toState:(ASVideoNodePlayerState)toState -{ - if (_delegateFlags.delegateVideoNodeWillChangeState) { - [_delegate videoPlayerNode:self willChangeVideoNodeState:state toVideoNodeState:toState]; - } - - if (toState == ASVideoNodePlayerStateReadyToPlay) { - _duration = _videoNode.currentItem.duration; - [self updateDurationTimeLabel]; - } - - if (toState == ASVideoNodePlayerStatePlaying) { - _playbackButtonNode.buttonType = ASDefaultPlaybackButtonTypePause; - [self removeSpinner]; - } else if (toState != ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying && toState != ASVideoNodePlayerStateReadyToPlay) { - _playbackButtonNode.buttonType = ASDefaultPlaybackButtonTypePlay; - } - - if (toState == ASVideoNodePlayerStateLoading || toState == ASVideoNodePlayerStateInitialLoading) { - [self showSpinner]; - } - - if (toState == ASVideoNodePlayerStateReadyToPlay || toState == ASVideoNodePlayerStatePaused || toState == ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying) { - [self removeSpinner]; - } -} - -- (BOOL)videoNode:(ASVideoNode *)videoNode shouldChangePlayerStateTo:(ASVideoNodePlayerState)state -{ - if (_delegateFlags.delegateVideoNodeShouldChangeState) { - return [_delegate videoPlayerNode:self shouldChangeVideoNodeStateTo:state]; - } - return YES; -} - -- (void)videoNode:(ASVideoNode *)videoNode didPlayToTimeInterval:(NSTimeInterval)timeInterval -{ - if (_delegateFlags.delegateVideoNodeDidPlayToTime) { - [_delegate videoPlayerNode:self didPlayToTime:_videoNode.player.currentTime]; - } - - if (_isSeeking) { - return; - } - - if (_elapsedTextNode) { - [self updateElapsedTimeLabel:timeInterval]; - } - - if (_scrubberNode) { - [(UISlider*)_scrubberNode.view setValue:( timeInterval / CMTimeGetSeconds(_duration) ) animated:NO]; - } -} - -- (void)videoDidPlayToEnd:(ASVideoNode *)videoNode -{ - if (_delegateFlags.delegateVideoNodePlaybackDidFinish) { - [_delegate videoPlayerNodeDidPlayToEnd:self]; - } -} - -- (void)didTapVideoNode:(ASVideoNode *)videoNode -{ - if (_delegateFlags.delegateDidTapVideoPlayerNode) { - [_delegate didTapVideoPlayerNode:self]; - } else { - [self togglePlayPause]; - } -} - -- (void)videoNode:(ASVideoNode *)videoNode didSetCurrentItem:(AVPlayerItem *)currentItem -{ - if (_delegateFlags.delegateVideoPlayerNodeDidSetCurrentItem) { - [_delegate videoPlayerNode:self didSetCurrentItem:currentItem]; - } -} - -- (void)videoNode:(ASVideoNode *)videoNode didStallAtTimeInterval:(NSTimeInterval)timeInterval -{ - if (_delegateFlags.delegateVideoPlayerNodeDidStallAtTimeInterval) { - [_delegate videoPlayerNode:self didStallAtTimeInterval:timeInterval]; - } -} - -- (void)videoNodeDidStartInitialLoading:(ASVideoNode *)videoNode -{ - if (_delegateFlags.delegateVideoPlayerNodeDidStartInitialLoading) { - [_delegate videoPlayerNodeDidStartInitialLoading:self]; - } -} - -- (void)videoNodeDidFinishInitialLoading:(ASVideoNode *)videoNode -{ - if (_delegateFlags.delegateVideoPlayerNodeDidFinishInitialLoading) { - [_delegate videoPlayerNodeDidFinishInitialLoading:self]; - } -} - -- (void)videoNodeDidRecoverFromStall:(ASVideoNode *)videoNode -{ - if (_delegateFlags.delegateVideoPlayerNodeDidRecoverFromStall) { - [_delegate videoPlayerNodeDidRecoverFromStall:self]; - } -} - -#pragma mark - Actions -- (void)togglePlayPause -{ - if (_videoNode.playerState == ASVideoNodePlayerStatePlaying) { - [_videoNode pause]; - } else { - [_videoNode play]; - } -} - -- (void)showSpinner -{ - ASLockScopeSelf(); - - if (!_spinnerNode) { - __weak __typeof__(self) weakSelf = self; - _spinnerNode = [[ASDisplayNode alloc] initWithViewBlock:^UIView *{ - __typeof__(self) strongSelf = weakSelf; - UIActivityIndicatorView *spinnnerView = [[UIActivityIndicatorView alloc] init]; - spinnnerView.backgroundColor = [UIColor clearColor]; - - if (_delegateFlags.delegateSpinnerTintColor) { - spinnnerView.color = [_delegate videoPlayerNodeSpinnerTint:strongSelf]; - } else { - spinnnerView.color = _defaultControlsColor; - } - - if (_delegateFlags.delegateSpinnerStyle) { - spinnnerView.activityIndicatorViewStyle = [_delegate videoPlayerNodeSpinnerStyle:strongSelf]; - } - - return spinnnerView; - }]; - _spinnerNode.style.preferredSize = CGSizeMake(44.0, 44.0); - - const auto spinnerNode = _spinnerNode; - { - ASUnlockScope(self); - [self addSubnode:spinnerNode]; - [self setNeedsLayout]; - } - } - [(UIActivityIndicatorView *)_spinnerNode.view startAnimating]; -} - -- (void)removeSpinner -{ - ASDisplayNode *spinnerNode = nil; - { - ASLockScopeSelf(); - if (!_spinnerNode) { - return; - } - - spinnerNode = _spinnerNode; - _spinnerNode = nil; - } - - [spinnerNode removeFromSupernode]; -} - -- (void)didTapPlaybackButton:(ASControlNode*)node -{ - [self togglePlayPause]; -} - -- (void)didTapFullScreenButton:(ASButtonNode*)node -{ - if (_delegateFlags.delegateDidTapFullScreenButtonNode) { - [_delegate didTapFullScreenButtonNode:node]; - } -} - -- (void)beginSeek -{ - _isSeeking = YES; -} - -- (void)endSeek -{ - _isSeeking = NO; -} - -- (void)seekTimeDidChange:(UISlider*)slider -{ - CGFloat percentage = slider.value * 100; - [self seekToTime:percentage]; -} - -#pragma mark - Public API -- (void)seekToTime:(CGFloat)percentComplete -{ - CGFloat seconds = ( CMTimeGetSeconds(_duration) * percentComplete ) / 100; - - [self updateElapsedTimeLabel:seconds]; - [_videoNode.player seekToTime:CMTimeMakeWithSeconds(seconds, _videoNode.periodicTimeObserverTimescale)]; - - if (_videoNode.playerState != ASVideoNodePlayerStatePlaying) { - [self togglePlayPause]; - } -} - -- (void)play -{ - [_videoNode play]; -} - -- (void)pause -{ - [_videoNode pause]; -} - -- (BOOL)isPlaying -{ - return [_videoNode isPlaying]; -} - -- (void)resetToPlaceholder -{ - [_videoNode resetToPlaceholder]; -} - -- (NSArray *)controlsForLayoutSpec -{ - NSMutableArray *controls = [[NSMutableArray alloc] initWithCapacity:_cachedControls.count]; - - if (_cachedControls[ @(ASVideoPlayerNodeControlTypePlaybackButton) ]) { - [controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypePlaybackButton) ]]; - } - - if (_cachedControls[ @(ASVideoPlayerNodeControlTypeElapsedText) ]) { - [controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypeElapsedText) ]]; - } - - if (_cachedControls[ @(ASVideoPlayerNodeControlTypeScrubber) ]) { - [controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypeScrubber) ]]; - } - - if (_cachedControls[ @(ASVideoPlayerNodeControlTypeDurationText) ]) { - [controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypeDurationText) ]]; - } - - if (_cachedControls[ @(ASVideoPlayerNodeControlTypeFullScreenButton) ]) { - [controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypeFullScreenButton) ]]; - } - - return controls; -} - - -#pragma mark - Layout - -- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize -{ - CGSize maxSize = constrainedSize.max; - - // Prevent crashes through if infinite width or height - if (isinf(maxSize.width) || isinf(maxSize.height)) { - ASDisplayNodeAssert(NO, @"Infinite width or height in ASVideoPlayerNode"); - maxSize = CGSizeZero; - } - - _videoNode.style.preferredSize = maxSize; - - ASLayoutSpec *layoutSpec; - if (_delegateFlags.delegateLayoutSpecForControls) { - layoutSpec = [_delegate videoPlayerNodeLayoutSpec:self forControls:_cachedControls forMaximumSize:maxSize]; - } else { - layoutSpec = [self defaultLayoutSpecThatFits:maxSize]; - } - - NSMutableArray *children = [[NSMutableArray alloc] init]; - - if (_spinnerNode) { - ASCenterLayoutSpec *centerLayoutSpec = [ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY sizingOptions:ASCenterLayoutSpecSizingOptionDefault child:_spinnerNode]; - centerLayoutSpec.style.preferredSize = maxSize; - [children addObject:centerLayoutSpec]; - } - - ASOverlayLayoutSpec *overlaySpec = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:_videoNode overlay:layoutSpec]; - overlaySpec.style.preferredSize = maxSize; - [children addObject:overlaySpec]; - - return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:children]; -} - -- (ASLayoutSpec *)defaultLayoutSpecThatFits:(CGSize)maxSize -{ - _scrubberNode.style.preferredSize = CGSizeMake(maxSize.width, 44.0); - - ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; - spacer.style.flexGrow = 1.0; - - ASStackLayoutSpec *controlbarSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal - spacing:10.0 - justifyContent:ASStackLayoutJustifyContentStart - alignItems:ASStackLayoutAlignItemsCenter - children: [self controlsForLayoutSpec] ]; - controlbarSpec.style.alignSelf = ASStackLayoutAlignSelfStretch; - - UIEdgeInsets insets = UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0); - - ASInsetLayoutSpec *controlbarInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:controlbarSpec]; - - controlbarInsetSpec.style.alignSelf = ASStackLayoutAlignSelfStretch; - - ASStackLayoutSpec *mainVerticalStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical - spacing:0.0 - justifyContent:ASStackLayoutJustifyContentStart - alignItems:ASStackLayoutAlignItemsStart - children:@[spacer,controlbarInsetSpec]]; - - return mainVerticalStack; -} - -#pragma mark - Properties -- (id)delegate -{ - return _delegate; -} - -- (void)setDelegate:(id)delegate -{ - if (delegate == _delegate) { - return; - } - - _delegate = delegate; - - if (_delegate == nil) { - memset(&_delegateFlags, 0, sizeof(_delegateFlags)); - } else { - _delegateFlags.delegateNeededDefaultControls = [_delegate respondsToSelector:@selector(videoPlayerNodeNeededDefaultControls:)]; - _delegateFlags.delegateCustomControls = [_delegate respondsToSelector:@selector(videoPlayerNodeCustomControls:)]; - _delegateFlags.delegateSpinnerTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeSpinnerTint:)]; - _delegateFlags.delegateSpinnerStyle = [_delegate respondsToSelector:@selector(videoPlayerNodeSpinnerStyle:)]; - _delegateFlags.delegateScrubberMaximumTrackTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberMaximumTrackTint:)]; - _delegateFlags.delegateScrubberMinimumTrackTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberMinimumTrackTint:)]; - _delegateFlags.delegateScrubberThumbTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberThumbTint:)]; - _delegateFlags.delegateScrubberThumbImage = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberThumbImage:)]; - _delegateFlags.delegateTimeLabelAttributes = [_delegate respondsToSelector:@selector(videoPlayerNodeTimeLabelAttributes:timeLabelType:)]; - _delegateFlags.delegateLayoutSpecForControls = [_delegate respondsToSelector:@selector(videoPlayerNodeLayoutSpec:forControls:forMaximumSize:)]; - _delegateFlags.delegateVideoNodeDidPlayToTime = [_delegate respondsToSelector:@selector(videoPlayerNode:didPlayToTime:)]; - _delegateFlags.delegateVideoNodeWillChangeState = [_delegate respondsToSelector:@selector(videoPlayerNode:willChangeVideoNodeState:toVideoNodeState:)]; - _delegateFlags.delegateVideoNodePlaybackDidFinish = [_delegate respondsToSelector:@selector(videoPlayerNodeDidPlayToEnd:)]; - _delegateFlags.delegateVideoNodeShouldChangeState = [_delegate respondsToSelector:@selector(videoPlayerNode:shouldChangeVideoNodeStateTo:)]; - _delegateFlags.delegateTimeLabelAttributedString = [_delegate respondsToSelector:@selector(videoPlayerNode:timeStringForTimeLabelType:forTime:)]; - _delegateFlags.delegatePlaybackButtonTint = [_delegate respondsToSelector:@selector(videoPlayerNodePlaybackButtonTint:)]; - _delegateFlags.delegateFullScreenButtonImage = [_delegate respondsToSelector:@selector(videoPlayerNodeFullScreenButtonImage:)]; - _delegateFlags.delegateDidTapVideoPlayerNode = [_delegate respondsToSelector:@selector(didTapVideoPlayerNode:)]; - _delegateFlags.delegateDidTapFullScreenButtonNode = [_delegate respondsToSelector:@selector(didTapFullScreenButtonNode:)]; - _delegateFlags.delegateVideoPlayerNodeDidSetCurrentItem = [_delegate respondsToSelector:@selector(videoPlayerNode:didSetCurrentItem:)]; - _delegateFlags.delegateVideoPlayerNodeDidStallAtTimeInterval = [_delegate respondsToSelector:@selector(videoPlayerNode:didStallAtTimeInterval:)]; - _delegateFlags.delegateVideoPlayerNodeDidStartInitialLoading = [_delegate respondsToSelector:@selector(videoPlayerNodeDidStartInitialLoading:)]; - _delegateFlags.delegateVideoPlayerNodeDidFinishInitialLoading = [_delegate respondsToSelector:@selector(videoPlayerNodeDidFinishInitialLoading:)]; - _delegateFlags.delegateVideoPlayerNodeDidRecoverFromStall = [_delegate respondsToSelector:@selector(videoPlayerNodeDidRecoverFromStall:)]; - } -} - -- (void)setControlsDisabled:(BOOL)controlsDisabled -{ - if (_controlsDisabled == controlsDisabled) { - return; - } - - _controlsDisabled = controlsDisabled; - - if (_controlsDisabled && _cachedControls.count > 0) { - [self removeControls]; - } else if (!_controlsDisabled) { - [self createControls]; - } -} - -- (void)setShouldAutoPlay:(BOOL)shouldAutoPlay -{ - if (_shouldAutoPlay == shouldAutoPlay) { - return; - } - _shouldAutoPlay = shouldAutoPlay; - _videoNode.shouldAutoplay = _shouldAutoPlay; -} - -- (void)setShouldAutoRepeat:(BOOL)shouldAutoRepeat -{ - if (_shouldAutoRepeat == shouldAutoRepeat) { - return; - } - _shouldAutoRepeat = shouldAutoRepeat; - _videoNode.shouldAutorepeat = _shouldAutoRepeat; -} - -- (void)setMuted:(BOOL)muted -{ - if (_muted == muted) { - return; - } - _muted = muted; - _videoNode.muted = _muted; -} - -- (void)setPeriodicTimeObserverTimescale:(int32_t)periodicTimeObserverTimescale -{ - if (_periodicTimeObserverTimescale == periodicTimeObserverTimescale) { - return; - } - _periodicTimeObserverTimescale = periodicTimeObserverTimescale; - _videoNode.periodicTimeObserverTimescale = _periodicTimeObserverTimescale; -} - -- (NSString *)gravity -{ - if (_gravity == nil) { - _gravity = _videoNode.gravity; - } - return _gravity; -} - -- (void)setGravity:(NSString *)gravity -{ - if (_gravity == gravity) { - return; - } - _gravity = gravity; - _videoNode.gravity = _gravity; -} - -- (ASVideoNodePlayerState)playerState -{ - return _videoNode.playerState; -} - -- (BOOL)shouldAggressivelyRecoverFromStall -{ - return _videoNode.shouldAggressivelyRecoverFromStall; -} - -- (void) setPlaceholderImageURL:(NSURL *)placeholderImageURL -{ - _videoNode.URL = placeholderImageURL; -} - -- (NSURL*) placeholderImageURL -{ - return _videoNode.URL; -} - -- (ASVideoNode*)videoNode -{ - return _videoNode; -} - -- (void)setShouldAggressivelyRecoverFromStall:(BOOL)shouldAggressivelyRecoverFromStall -{ - if (_shouldAggressivelyRecoverFromStall == shouldAggressivelyRecoverFromStall) { - return; - } - _shouldAggressivelyRecoverFromStall = shouldAggressivelyRecoverFromStall; - _videoNode.shouldAggressivelyRecoverFromStall = _shouldAggressivelyRecoverFromStall; -} - -#pragma mark - Helpers -- (NSString *)timeStringForCMTime:(CMTime)time forTimeLabelType:(ASVideoPlayerNodeControlType)type -{ - if (!CMTIME_IS_VALID(time)) { - return @"00:00"; - } - if (_delegateFlags.delegateTimeLabelAttributedString) { - return [_delegate videoPlayerNode:self timeStringForTimeLabelType:type forTime:time]; - } - - NSUInteger dTotalSeconds = CMTimeGetSeconds(time); - - NSUInteger dHours = floor(dTotalSeconds / 3600); - NSUInteger dMinutes = floor(dTotalSeconds % 3600 / 60); - NSUInteger dSeconds = floor(dTotalSeconds % 3600 % 60); - - NSString *videoDurationText; - if (dHours > 0) { - videoDurationText = [NSString stringWithFormat:@"%i:%02i:%02i", (int)dHours, (int)dMinutes, (int)dSeconds]; - } else { - videoDurationText = [NSString stringWithFormat:@"%02i:%02i", (int)dMinutes, (int)dSeconds]; - } - return videoDurationText; -} - -@end - -#endif // TARGET_OS_IOS -#endif - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASViewController.h b/submodules/AsyncDisplayKit/Source/ASViewController.h deleted file mode 100644 index 18c1be5456..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASViewController.h +++ /dev/null @@ -1,96 +0,0 @@ -// -// ASViewController.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import -#import - -@class ASTraitCollection; - -NS_ASSUME_NONNULL_BEGIN - -typedef ASTraitCollection * _Nonnull (^ASDisplayTraitsForTraitCollectionBlock)(UITraitCollection *traitCollection); -typedef ASTraitCollection * _Nonnull (^ASDisplayTraitsForTraitWindowSizeBlock)(CGSize windowSize); - -/** - * ASViewController allows you to have a completely node backed hierarchy. It automatically - * handles @c ASVisibilityDepth, automatic range mode and propogating @c ASDisplayTraits to contained nodes. - * - * You can opt-out of node backed hierarchy and use it like a normal UIViewController. - * More importantly, you can use it as a base class for all of your view controllers among which some use a node hierarchy and some don't. - * See examples/ASDKgram project for actual implementation. - */ -@interface ASViewController<__covariant DisplayNodeType : ASDisplayNode *> : UIViewController - -/** - * ASViewController initializer. - * - * @param node An ASDisplayNode which will provide the root view (self.view) - * @return An ASViewController instance whose root view will be backed by the provided ASDisplayNode. - * - * @see ASVisibilityDepth - */ -- (instancetype)initWithNode:(DisplayNodeType)node NS_DESIGNATED_INITIALIZER; - -NS_ASSUME_NONNULL_END - -/** - * @return node Returns the ASDisplayNode which provides the backing view to the view controller. - */ -@property (nonatomic, readonly, null_unspecified) DisplayNodeType node; - -NS_ASSUME_NONNULL_BEGIN - -/** - * Set this block to customize the ASDisplayTraits returned when the VC transitions to the given traitCollection. - */ -@property (nonatomic, copy) ASDisplayTraitsForTraitCollectionBlock overrideDisplayTraitsWithTraitCollection; - -/** - * Set this block to customize the ASDisplayTraits returned when the VC transitions to the given window size. - */ -@property (nonatomic, copy) ASDisplayTraitsForTraitWindowSizeBlock overrideDisplayTraitsWithWindowSize ASDISPLAYNODE_DEPRECATED_MSG("This property is actually never accessed inside the framework"); - -/** - * @abstract Passthrough property to the the .interfaceState of the node. - * @return The current ASInterfaceState of the node, indicating whether it is visible and other situational properties. - * @see ASInterfaceState - */ -@property (nonatomic, readonly) ASInterfaceState interfaceState; - - -// AsyncDisplayKit 2.0 BETA: This property is still being tested, but it allows -// blocking as a view controller becomes visible to ensure no placeholders flash onscreen. -// Refer to examples/SynchronousConcurrency, AsyncViewController.m -@property (nonatomic) BOOL neverShowPlaceholders; - -/* Custom container UIViewController subclasses can use this property to add to the overlay - that UIViewController calculates for the safeAreaInsets for contained view controllers. - */ -@property(nonatomic) UIEdgeInsets additionalSafeAreaInsets; - -@end - -@interface ASViewController (ASRangeControllerUpdateRangeProtocol) - -/** - * Automatically adjust range mode based on view events. If you set this to YES, the view controller or its node - * must conform to the ASRangeControllerUpdateRangeProtocol. - * - * Default value is YES *if* node or view controller conform to ASRangeControllerUpdateRangeProtocol otherwise it is NO. - */ -@property (nonatomic) BOOL automaticallyAdjustRangeModeBasedOnViewEvents; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASViewController.mm b/submodules/AsyncDisplayKit/Source/ASViewController.mm deleted file mode 100644 index 5992c050f1..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASViewController.mm +++ /dev/null @@ -1,362 +0,0 @@ -// -// ASViewController.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK -#import -#import -#import -#import -#import -#import -#import -#import - -@implementation ASViewController -{ - BOOL _ensureDisplayed; - BOOL _automaticallyAdjustRangeModeBasedOnViewEvents; - BOOL _parentManagesVisibilityDepth; - NSInteger _visibilityDepth; - BOOL _selfConformsToRangeModeProtocol; - BOOL _nodeConformsToRangeModeProtocol; - UIEdgeInsets _fallbackAdditionalSafeAreaInsets; -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wobjc-designated-initializers" - -- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil -{ - if (!(self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) { - return nil; - } - - [self _initializeInstance]; - - return self; -} - -- (instancetype)initWithCoder:(NSCoder *)aDecoder -{ - if (!(self = [super initWithCoder:aDecoder])) { - return nil; - } - - [self _initializeInstance]; - - return self; -} - -#pragma clang diagnostic pop - -- (instancetype)initWithNode:(ASDisplayNode *)node -{ - if (!(self = [super initWithNibName:nil bundle:nil])) { - return nil; - } - - _node = node; - [self _initializeInstance]; - - return self; -} - -- (void)_initializeInstance -{ - if (_node == nil) { - return; - } - - _node.viewControllerRoot = YES; - - _selfConformsToRangeModeProtocol = [self conformsToProtocol:@protocol(ASRangeControllerUpdateRangeProtocol)]; - _nodeConformsToRangeModeProtocol = [_node conformsToProtocol:@protocol(ASRangeControllerUpdateRangeProtocol)]; - _automaticallyAdjustRangeModeBasedOnViewEvents = _selfConformsToRangeModeProtocol || _nodeConformsToRangeModeProtocol; - - _fallbackAdditionalSafeAreaInsets = UIEdgeInsetsZero; - - // In case the node will get loaded - if (_node.nodeLoaded) { - // Node already loaded the view - [self view]; - } else { - // If the node didn't load yet add ourselves as on did load observer to load the view in case the node gets loaded - // before the view controller - __weak __typeof__(self) weakSelf = self; - [_node onDidLoad:^(__kindof ASDisplayNode * _Nonnull node) { - if ([weakSelf isViewLoaded] == NO) { - [weakSelf view]; - } - }]; - } -} - -- (void)dealloc -{ - ASPerformBackgroundDeallocation(&_node); -} - -- (void)loadView -{ - // Apple applies a frame and autoresizing masks we need. Allocating a view is not - // nearly as expensive as adding and removing it from a hierarchy, and fortunately - // we can avoid that here. Enabling layerBacking on a single node in the hierarchy - // will have a greater performance benefit than the impact of this transient view. - [super loadView]; - - if (_node == nil) { - return; - } - - ASDisplayNodeAssertTrue(!_node.layerBacked); - - UIView *view = self.view; - CGRect frame = view.frame; - UIViewAutoresizing autoresizingMask = view.autoresizingMask; - - // We have what we need, so now create and assign the view we actually want. - view = _node.view; - _node.frame = frame; - _node.autoresizingMask = autoresizingMask; - self.view = view; - - // ensure that self.node has a valid trait collection before a subclass's implementation of viewDidLoad. - // Any subnodes added in viewDidLoad will then inherit the proper environment. - ASPrimitiveTraitCollection traitCollection = [self primitiveTraitCollectionForUITraitCollection:self.traitCollection]; - [self propagateNewTraitCollection:traitCollection]; -} - -- (void)viewWillLayoutSubviews -{ - [super viewWillLayoutSubviews]; - - // Before layout, make sure that our trait collection containerSize actually matches the size of our bounds. - // If not, we need to update the traits and propagate them. - - CGSize boundsSize = self.view.bounds.size; - if (CGSizeEqualToSize(self.node.primitiveTraitCollection.containerSize, boundsSize) == NO) { - [UIView performWithoutAnimation:^{ - ASPrimitiveTraitCollection traitCollection = [self primitiveTraitCollectionForUITraitCollection:self.traitCollection]; - traitCollection.containerSize = boundsSize; - - // this method will call measure - [self propagateNewTraitCollection:traitCollection]; - }]; - } else { - // Call layoutThatFits: to let the node prepare for a layout that will happen shortly in the layout pass of the view. - // If the node's constrained size didn't change between the last layout pass it's a no-op - [_node layoutThatFits:[self nodeConstrainedSize]]; - } -} - -- (void)viewDidLayoutSubviews -{ - if (_ensureDisplayed && self.neverShowPlaceholders) { - _ensureDisplayed = NO; - [_node recursivelyEnsureDisplaySynchronously:YES]; - } - [super viewDidLayoutSubviews]; - - if (!AS_AT_LEAST_IOS11) { - [self _updateNodeFallbackSafeArea]; - } -} - -- (void)_updateNodeFallbackSafeArea -{ - UIEdgeInsets safeArea = UIEdgeInsetsMake(self.topLayoutGuide.length, 0, self.bottomLayoutGuide.length, 0); - UIEdgeInsets additionalInsets = self.additionalSafeAreaInsets; - - safeArea = ASConcatInsets(safeArea, additionalInsets); - - _node.fallbackSafeAreaInsets = safeArea; -} - -ASVisibilityDidMoveToParentViewController; - -- (void)viewWillAppear:(BOOL)animated -{ - as_activity_create_for_scope("ASViewController will appear"); - as_log_debug(ASNodeLog(), "View controller %@ will appear", self); - - [super viewWillAppear:animated]; - - _ensureDisplayed = YES; - - // A layout pass is forced this early to get nodes like ASCollectionNode, ASTableNode etc. - // into the hierarchy before UIKit applies the scroll view inset adjustments, if automatic subnode management - // is enabled. Otherwise the insets would not be applied. - [_node.view layoutIfNeeded]; - - if (_parentManagesVisibilityDepth == NO) { - [self setVisibilityDepth:0]; - } -} - -ASVisibilitySetVisibilityDepth; - -ASVisibilityViewDidDisappearImplementation; - -ASVisibilityDepthImplementation; - -- (void)visibilityDepthDidChange -{ - ASLayoutRangeMode rangeMode = ASLayoutRangeModeForVisibilityDepth(self.visibilityDepth); -#if ASEnableVerboseLogging - NSString *rangeModeString; - switch (rangeMode) { - case ASLayoutRangeModeMinimum: - rangeModeString = @"Minimum"; - break; - - case ASLayoutRangeModeFull: - rangeModeString = @"Full"; - break; - - case ASLayoutRangeModeVisibleOnly: - rangeModeString = @"Visible Only"; - break; - - case ASLayoutRangeModeLowMemory: - rangeModeString = @"Low Memory"; - break; - - default: - break; - } - as_log_verbose(ASNodeLog(), "Updating visibility of %@ to: %@ (visibility depth: %zd)", self, rangeModeString, self.visibilityDepth); -#endif - [self updateCurrentRangeModeWithModeIfPossible:rangeMode]; -} - -#pragma mark - Automatic range mode - -- (BOOL)automaticallyAdjustRangeModeBasedOnViewEvents -{ - return _automaticallyAdjustRangeModeBasedOnViewEvents; -} - -- (void)setAutomaticallyAdjustRangeModeBasedOnViewEvents:(BOOL)automaticallyAdjustRangeModeBasedOnViewEvents -{ - if (automaticallyAdjustRangeModeBasedOnViewEvents != _automaticallyAdjustRangeModeBasedOnViewEvents) { - if (automaticallyAdjustRangeModeBasedOnViewEvents && _selfConformsToRangeModeProtocol == NO && _nodeConformsToRangeModeProtocol == NO) { - NSLog(@"Warning: automaticallyAdjustRangeModeBasedOnViewEvents set to YES in %@, but range mode updating is not possible because neither view controller nor node %@ conform to ASRangeControllerUpdateRangeProtocol.", self, _node); - } - _automaticallyAdjustRangeModeBasedOnViewEvents = automaticallyAdjustRangeModeBasedOnViewEvents; - } -} - -- (void)updateCurrentRangeModeWithModeIfPossible:(ASLayoutRangeMode)rangeMode -{ - if (!_automaticallyAdjustRangeModeBasedOnViewEvents) { - return; - } - - if (_selfConformsToRangeModeProtocol) { - id rangeUpdater = (id)self; - [rangeUpdater updateCurrentRangeWithMode:rangeMode]; - } - - if (_nodeConformsToRangeModeProtocol) { - id rangeUpdater = (id)_node; - [rangeUpdater updateCurrentRangeWithMode:rangeMode]; - } -} - -#pragma mark - Layout Helpers - -- (ASSizeRange)nodeConstrainedSize -{ - return ASSizeRangeMake(self.view.bounds.size); -} - -- (ASInterfaceState)interfaceState -{ - return _node.interfaceState; -} - -- (UIEdgeInsets)additionalSafeAreaInsets -{ - if (AS_AVAILABLE_IOS(11.0)) { - return super.additionalSafeAreaInsets; - } - - return _fallbackAdditionalSafeAreaInsets; -} - -- (void)setAdditionalSafeAreaInsets:(UIEdgeInsets)additionalSafeAreaInsets -{ - if (AS_AVAILABLE_IOS(11.0)) { - [super setAdditionalSafeAreaInsets:additionalSafeAreaInsets]; - } else { - _fallbackAdditionalSafeAreaInsets = additionalSafeAreaInsets; - [self _updateNodeFallbackSafeArea]; - } -} - -#pragma mark - ASTraitEnvironment - -- (ASPrimitiveTraitCollection)primitiveTraitCollectionForUITraitCollection:(UITraitCollection *)traitCollection -{ - if (self.overrideDisplayTraitsWithTraitCollection) { - ASTraitCollection *asyncTraitCollection = self.overrideDisplayTraitsWithTraitCollection(traitCollection); - return [asyncTraitCollection primitiveTraitCollection]; - } - - ASDisplayNodeAssertMainThread(); - ASPrimitiveTraitCollection asyncTraitCollection = ASPrimitiveTraitCollectionFromUITraitCollection(traitCollection); - asyncTraitCollection.containerSize = self.view.frame.size; - return asyncTraitCollection; -} - -- (void)propagateNewTraitCollection:(ASPrimitiveTraitCollection)traitCollection -{ - ASPrimitiveTraitCollection oldTraitCollection = self.node.primitiveTraitCollection; - - if (ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(traitCollection, oldTraitCollection) == NO) { - as_activity_scope_verbose(as_activity_create("Propagate ASViewController trait collection", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT)); - as_log_debug(ASNodeLog(), "Propagating new traits for %@: %@", self, NSStringFromASPrimitiveTraitCollection(traitCollection)); - self.node.primitiveTraitCollection = traitCollection; - - NSArray> *children = [self.node sublayoutElements]; - for (id child in children) { - ASTraitCollectionPropagateDown(child, traitCollection); - } - - // Once we've propagated all the traits, layout this node. - // Remeasure the node with the latest constrained size – old constrained size may be incorrect. - as_activity_scope_verbose(as_activity_create("Layout ASViewController node with new traits", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT)); - [_node layoutThatFits:[self nodeConstrainedSize]]; - } -} - -- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection -{ - [super traitCollectionDidChange:previousTraitCollection]; - - ASPrimitiveTraitCollection traitCollection = [self primitiveTraitCollectionForUITraitCollection:self.traitCollection]; - traitCollection.containerSize = self.view.bounds.size; - [self propagateNewTraitCollection:traitCollection]; -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation -{ - [super didRotateFromInterfaceOrientation:fromInterfaceOrientation]; - - ASPrimitiveTraitCollection traitCollection = _node.primitiveTraitCollection; - traitCollection.containerSize = self.view.bounds.size; - [self propagateNewTraitCollection:traitCollection]; -} -#pragma clang diagnostic pop - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASVisibilityProtocols.h b/submodules/AsyncDisplayKit/Source/ASVisibilityProtocols.h deleted file mode 100644 index 837e848269..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASVisibilityProtocols.h +++ /dev/null @@ -1,145 +0,0 @@ -// -// ASVisibilityProtocols.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@class UIViewController; - -AS_EXTERN ASLayoutRangeMode ASLayoutRangeModeForVisibilityDepth(NSUInteger visibilityDepth); - -/** - * ASVisibilityDepth - * - * @discussion "Visibility Depth" represents the number of user actions required to make an ASDisplayNode or - * ASViewController visibile. AsyncDisplayKit uses this information to intelligently manage memory and focus - * resources where they are most visible to the user. - * - * The ASVisibilityDepth protocol describes how custom view controllers can integrate with this system. - * - * Parent view controllers should also implement @c ASManagesChildVisibilityDepth - * - * @see ASManagesChildVisibilityDepth - */ - -@protocol ASVisibilityDepth - -/** - * Visibility depth - * - * @discussion Represents the number of user actions necessary to reach the view controller. An increased visibility - * depth indicates a higher number of user interactions for the view controller to be visible again. For example, - * an onscreen navigation controller's top view controller should have a visibility depth of 0. The view controller - * one from the top should have a visibility deptch of 1 as should the root view controller in the stack (because - * the user can hold the back button to pop to the root view controller). - * - * Visibility depth is used to automatically adjust ranges on range controllers (and thus free up memory) and can - * be used to reduce memory usage of other items as well. - */ -- (NSInteger)visibilityDepth; - -/** - * Called when visibility depth changes - * - * @discussion @c visibilityDepthDidChange is called whenever the visibility depth of the represented view controller - * has changed. - * - * If implemented by a view controller container, use this method to notify child view controllers that their view - * depth has changed @see ASNavigationController.m - * - * If implemented on an ASViewController, use this method to reduce or increase the resources that your - * view controller uses. A higher visibility depth view controller should decrease it's resource usage, a lower - * visibility depth controller should pre-warm resources in preperation for a display at 0 depth. - * - * ASViewController implements this method and reduces / increases range mode of supporting nodes (such as ASCollectionNode - * and ASTableNode). - * - * @see visibilityDepth - */ -- (void)visibilityDepthDidChange; - -@end - -/** - * ASManagesChildVisibilityDepth - * - * @discussion A protocol which should be implemented by container view controllers to allow proper - * propagation of visibility depth - * - * @see ASVisibilityDepth - */ -@protocol ASManagesChildVisibilityDepth - -/** - * @abstract Container view controllers should adopt this protocol to indicate that they will manage their child's - * visibilityDepth. For example, ASNavigationController adopts this protocol and manages its childrens visibility - * depth. - * - * If you adopt this protocol, you *must* also emit visibilityDepthDidChange messages to child view controllers. - * - * @param childViewController Expected to return the visibility depth of the child view controller. - */ -- (NSInteger)visibilityDepthOfChildViewController:(UIViewController *)childViewController; - -@end - -#define ASVisibilitySetVisibilityDepth \ -- (void)setVisibilityDepth:(NSUInteger)visibilityDepth \ -{ \ - if (_visibilityDepth == visibilityDepth) { \ - return; \ - } \ - _visibilityDepth = visibilityDepth; \ - [self visibilityDepthDidChange]; \ -} - -#define ASVisibilityDepthImplementation \ -- (NSInteger)visibilityDepth \ -{ \ - if (self.parentViewController && _parentManagesVisibilityDepth == NO) { \ - _parentManagesVisibilityDepth = [self.parentViewController conformsToProtocol:@protocol(ASManagesChildVisibilityDepth)]; \ - } \ - \ - if (_parentManagesVisibilityDepth) { \ - return [(id )self.parentViewController visibilityDepthOfChildViewController:self]; \ - } \ - return _visibilityDepth; \ -} - -#define ASVisibilityViewDidDisappearImplementation \ -- (void)viewDidDisappear:(BOOL)animated \ -{ \ - [super viewDidDisappear:animated]; \ - \ - if (_parentManagesVisibilityDepth == NO) { \ - [self setVisibilityDepth:1]; \ - } \ -} - -#define ASVisibilityViewWillAppear \ -- (void)viewWillAppear:(BOOL)animated \ -{ \ - [super viewWillAppear:animated]; \ - \ - if (_parentManagesVisibilityDepth == NO) { \ - [self setVisibilityDepth:0]; \ - } \ -} - -#define ASVisibilityDidMoveToParentViewController \ -- (void)didMoveToParentViewController:(UIViewController *)parent \ -{ \ - [super didMoveToParentViewController:parent]; \ - _parentManagesVisibilityDepth = NO; \ - [self visibilityDepthDidChange]; \ -} - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASVisibilityProtocols.mm b/submodules/AsyncDisplayKit/Source/ASVisibilityProtocols.mm deleted file mode 100644 index b13a683bea..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASVisibilityProtocols.mm +++ /dev/null @@ -1,22 +0,0 @@ -// -// ASVisibilityProtocols.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -ASLayoutRangeMode ASLayoutRangeModeForVisibilityDepth(NSUInteger visibilityDepth) -{ - if (visibilityDepth == 0) { - return ASLayoutRangeModeFull; - } else if (visibilityDepth == 1) { - return ASLayoutRangeModeMinimum; - } else if (visibilityDepth == 2) { - return ASLayoutRangeModeVisibleOnly; - } - return ASLayoutRangeModeLowMemory; -} diff --git a/submodules/AsyncDisplayKit/Source/Private/ASWeakMap.h b/submodules/AsyncDisplayKit/Source/ASWeakMap.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/Private/ASWeakMap.h rename to submodules/AsyncDisplayKit/Source/ASWeakMap.h diff --git a/submodules/AsyncDisplayKit/Source/Private/ASWeakMap.mm b/submodules/AsyncDisplayKit/Source/ASWeakMap.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/Private/ASWeakMap.mm rename to submodules/AsyncDisplayKit/Source/ASWeakMap.mm index 3110b13d37..1267d8ff69 100644 --- a/submodules/AsyncDisplayKit/Source/Private/ASWeakMap.mm +++ b/submodules/AsyncDisplayKit/Source/ASWeakMap.mm @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import +#import "ASWeakMap.h" @interface ASWeakMapEntry () @property (nonatomic, readonly) id key; diff --git a/submodules/AsyncDisplayKit/Source/Details/ASWeakProxy.h b/submodules/AsyncDisplayKit/Source/ASWeakProxy.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/Details/ASWeakProxy.h rename to submodules/AsyncDisplayKit/Source/ASWeakProxy.h diff --git a/submodules/AsyncDisplayKit/Source/Details/ASWeakProxy.mm b/submodules/AsyncDisplayKit/Source/ASWeakProxy.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/Details/ASWeakProxy.mm rename to submodules/AsyncDisplayKit/Source/ASWeakProxy.mm index 4a73408dd5..4a3b5c8a2a 100644 --- a/submodules/AsyncDisplayKit/Source/Details/ASWeakProxy.mm +++ b/submodules/AsyncDisplayKit/Source/ASWeakProxy.mm @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import +#import "ASWeakProxy.h" #import #import diff --git a/submodules/AsyncDisplayKit/Source/Details/ASWeakSet.mm b/submodules/AsyncDisplayKit/Source/ASWeakSet.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/Details/ASWeakSet.mm rename to submodules/AsyncDisplayKit/Source/ASWeakSet.mm diff --git a/submodules/AsyncDisplayKit/Source/AsyncDisplayKit+IGListKitMethods.h b/submodules/AsyncDisplayKit/Source/AsyncDisplayKit+IGListKitMethods.h deleted file mode 100644 index aa0de42510..0000000000 --- a/submodules/AsyncDisplayKit/Source/AsyncDisplayKit+IGListKitMethods.h +++ /dev/null @@ -1,64 +0,0 @@ -// -// AsyncDisplayKit+IGListKitMethods.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_IG_LIST_KIT - -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * If you are using AsyncDisplayKit with IGListKit, you should use - * these methods to provide implementations for methods like - * -cellForItemAtIndex: that don't apply when used with AsyncDisplayKit. - * - * Your section controllers should also conform to @c ASSectionController and your - * supplementary view sources should conform to @c ASSupplementaryNodeSource. - */ - -AS_SUBCLASSING_RESTRICTED -@interface ASIGListSectionControllerMethods : NSObject - -/** - * Call this for your section controller's @c cellForItemAtIndex: method. - */ -+ (__kindof UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index sectionController:(IGListSectionController *)sectionController; - -/** - * Call this for your section controller's @c sizeForItemAtIndex: method. - */ -+ (CGSize)sizeForItemAtIndex:(NSInteger)index; - -@end - -AS_SUBCLASSING_RESTRICTED -@interface ASIGListSupplementaryViewSourceMethods : NSObject - -/** - * Call this for your supplementary source's @c viewForSupplementaryElementOfKind:atIndex: method. - */ -+ (__kindof UICollectionReusableView *)viewForSupplementaryElementOfKind:(NSString *)elementKind - atIndex:(NSInteger)index - sectionController:(IGListSectionController *)sectionController; - -/** - * Call this for your supplementary source's @c sizeForSupplementaryViewOfKind:atIndex: method. - */ -+ (CGSize)sizeForSupplementaryViewOfKind:(NSString *)elementKind atIndex:(NSInteger)index; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/AsyncDisplayKit+IGListKitMethods.mm b/submodules/AsyncDisplayKit/Source/AsyncDisplayKit+IGListKitMethods.mm deleted file mode 100644 index 50d7a219f8..0000000000 --- a/submodules/AsyncDisplayKit/Source/AsyncDisplayKit+IGListKitMethods.mm +++ /dev/null @@ -1,53 +0,0 @@ -// -// AsyncDisplayKit+IGListKitMethods.m -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_IG_LIST_KIT - -#import "AsyncDisplayKit+IGListKitMethods.h" -#import -#import -#import - - -@implementation ASIGListSectionControllerMethods - -+ (__kindof UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index sectionController:(IGListSectionController *)sectionController -{ - // Cast to id for backwards-compatibility until 3.0.0 is officially released – IGListSectionType was removed. This is safe. - return [sectionController.collectionContext dequeueReusableCellOfClass:[_ASCollectionViewCell class] forSectionController:(id)sectionController atIndex:index]; -} - -+ (CGSize)sizeForItemAtIndex:(NSInteger)index -{ - ASDisplayNodeFailAssert(@"Did not expect %@ to be called.", NSStringFromSelector(_cmd)); - return CGSizeZero; -} - -@end - -@implementation ASIGListSupplementaryViewSourceMethods - -+ (__kindof UICollectionReusableView *)viewForSupplementaryElementOfKind:(NSString *)elementKind - atIndex:(NSInteger)index - sectionController:(IGListSectionController *)sectionController -{ - return [sectionController.collectionContext dequeueReusableSupplementaryViewOfKind:elementKind forSectionController:(id)sectionController class:[_ASCollectionReusableView class] atIndex:index]; -} - -+ (CGSize)sizeForSupplementaryViewOfKind:(NSString *)elementKind atIndex:(NSInteger)index -{ - ASDisplayNodeFailAssert(@"Did not expect %@ to be called.", NSStringFromSelector(_cmd)); - return CGSizeZero; -} - -@end - -#endif // AS_IG_LIST_KIT diff --git a/submodules/AsyncDisplayKit/Source/AsyncDisplayKit.modulemap b/submodules/AsyncDisplayKit/Source/AsyncDisplayKit.modulemap deleted file mode 100644 index fd7d49e620..0000000000 --- a/submodules/AsyncDisplayKit/Source/AsyncDisplayKit.modulemap +++ /dev/null @@ -1,19 +0,0 @@ -framework module AsyncDisplayKit { - umbrella header "AsyncDisplayKit.h" - - export * - module * { - export * - } - - explicit module ASControlNode_Subclasses { - header "ASControlNode+Subclasses.h" - export * - } - - explicit module ASDisplayNode_Subclasses { - header "ASDisplayNode+Subclasses.h" - export * - } - -} diff --git a/submodules/AsyncDisplayKit/Source/Base/ASAssert.m.orig b/submodules/AsyncDisplayKit/Source/Base/ASAssert.m.orig deleted file mode 100644 index 45a3452180..0000000000 --- a/submodules/AsyncDisplayKit/Source/Base/ASAssert.m.orig +++ /dev/null @@ -1,72 +0,0 @@ -// -// ASAssert.m -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -<<<<<<< HEAD -#ifndef MINIMAL_ASDK -static _Thread_local int tls_mainThreadAssertionsDisabledCount; -#endif -======= -#if AS_TLS_AVAILABLE ->>>>>>> 565da7d4935740d12fc204aa061faf093831da1e - -static _Thread_local int tls_mainThreadAssertionsDisabledCount; -BOOL ASMainThreadAssertionsAreDisabled() { -#ifdef MINIMAL_ASDK - return false; -#else - return tls_mainThreadAssertionsDisabledCount > 0; -#endif -} - -void ASPushMainThreadAssertionsDisabled() { -#ifndef MINIMAL_ASDK - tls_mainThreadAssertionsDisabledCount += 1; -#endif -} - -void ASPopMainThreadAssertionsDisabled() { -#ifndef MINIMAL_ASDK - tls_mainThreadAssertionsDisabledCount -= 1; - ASDisplayNodeCAssert(tls_mainThreadAssertionsDisabledCount >= 0, @"Attempt to pop thread assertion-disabling without corresponding push."); -#endif -} - -#else - -#import - -static pthread_key_t ASMainThreadAssertionsDisabledKey() { - static pthread_key_t k; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - pthread_key_create(&k, NULL); - }); - return k; -} - -BOOL ASMainThreadAssertionsAreDisabled() { - return (pthread_getspecific(ASMainThreadAssertionsDisabledKey()) > 0); -} - -void ASPushMainThreadAssertionsDisabled() { - let key = ASMainThreadAssertionsDisabledKey(); - let oldVal = pthread_getspecific(key); - pthread_setspecific(key, oldVal + 1); -} - -void ASPopMainThreadAssertionsDisabled() { - let key = ASMainThreadAssertionsDisabledKey(); - let oldVal = pthread_getspecific(key); - pthread_setspecific(key, oldVal - 1); - ASDisplayNodeCAssert(oldVal > 0, @"Attempt to pop thread assertion-disabling without corresponding push."); -} - -#endif // AS_TLS_AVAILABLE diff --git a/submodules/AsyncDisplayKit/Source/Base/ASLog.h b/submodules/AsyncDisplayKit/Source/Base/ASLog.h deleted file mode 100644 index ccab4b8e58..0000000000 --- a/submodules/AsyncDisplayKit/Source/Base/ASLog.h +++ /dev/null @@ -1,163 +0,0 @@ -// -// ASLog.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import -#import -#import - -#ifndef ASEnableVerboseLogging - #define ASEnableVerboseLogging 0 -#endif - -/** - * Disable all logging. - * - * You should only use this function if the default log level is - * annoying during development. By default, logging is run at - * the appropriate system log level (see the os_log_* functions), - * so you do not need to worry generally about the performance - * implications of log messages. - * - * For example, virtually all log messages generated by Texture - * are at the `debug` log level, which the system - * disables in production. - */ -AS_EXTERN void ASDisableLogging(void); - -/** - * Restore logging that has been runtime-disabled via ASDisableLogging(). - * - * Logging can be disabled at runtime using the ASDisableLogging() function. - * This command restores logging to the level provided in the build - * configuration. This can be used in conjunction with ASDisableLogging() - * to allow logging to be toggled off and back on at runtime. - */ -AS_EXTERN void ASEnableLogging(void); - -/// Log for general node events e.g. interfaceState, didLoad. -#define ASNodeLogEnabled 1 -AS_EXTERN os_log_t ASNodeLog(void); - -/// Log for layout-specific events e.g. calculateLayout. -#define ASLayoutLogEnabled 1 -AS_EXTERN os_log_t ASLayoutLog(void); - -/// Log for display-specific events e.g. display queue batches. -#define ASDisplayLogEnabled 1 -AS_EXTERN os_log_t ASDisplayLog(void); - -/// Log for collection events e.g. reloadData, performBatchUpdates. -#define ASCollectionLogEnabled 1 -AS_EXTERN os_log_t ASCollectionLog(void); - -/// Log for ASNetworkImageNode and ASMultiplexImageNode events. -#define ASImageLoadingLogEnabled 1 -AS_EXTERN os_log_t ASImageLoadingLog(void); - -/// Specialized log for our main thread deallocation trampoline. -#define ASMainThreadDeallocationLogEnabled 0 -AS_EXTERN os_log_t ASMainThreadDeallocationLog(void); - -/** - * The activity tracing system changed a lot between iOS 9 and 10. - * In iOS 10, the system was merged with logging and became much more powerful - * and adopted a new API. - * - * The legacy API is visible, but its functionality is extremely limited and the API is so different - * that we don't bother with it. For example, activities described by os_activity_start/end are not - * reflected in the log whereas activities described by the newer - * os_activity_scope are. So unfortunately we must use these iOS 10 - * APIs to get meaningful logging data. - */ -#if OS_LOG_TARGET_HAS_10_12_FEATURES - -#define OS_ACTIVITY_NULLABLE nullable -#define AS_ACTIVITY_CURRENT OS_ACTIVITY_CURRENT -#define as_activity_scope(activity) os_activity_scope(activity) -#define as_activity_apply(activity, block) os_activity_apply(activity, block) -#define as_activity_create(description, parent_activity, flags) os_activity_create(description, parent_activity, flags) -#define as_activity_scope_enter(activity, statePtr) os_activity_scope_enter(activity, statePtr) -#define as_activity_scope_leave(statePtr) os_activity_scope_leave(statePtr) -#define as_activity_get_identifier(activity, outParentID) os_activity_get_identifier(activity, outParentID) - -#else - -#define OS_ACTIVITY_NULLABLE -#define AS_ACTIVITY_CURRENT OS_ACTIVITY_NULL -#define as_activity_scope(activity) -#define as_activity_apply(activity, block) -#define as_activity_create(description, parent_activity, flags) OS_ACTIVITY_NULL -#define as_activity_scope_enter(activity, statePtr) -#define as_activity_scope_leave(statePtr) -#define as_activity_get_identifier(activity, outParentID) (os_activity_id_t)0 - -#endif // OS_LOG_TARGET_HAS_10_12_FEATURES - -// Create activities only when verbose enabled. Doesn't materially impact performance, but good if we're cluttering up -// activity scopes and reducing readability. -#if ASEnableVerboseLogging - #define as_activity_scope_verbose(activity) as_activity_scope(activity) -#else - #define as_activity_scope_verbose(activity) -#endif - -// Convenience for: as_activity_scope(as_activity_create(description, AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT)) -#define as_activity_create_for_scope(description) \ - as_activity_scope(as_activity_create(description, AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT)) - -/** - * The logging macros are not guarded by deployment-target checks like the activity macros are, but they are - * only available on iOS >= 9 at runtime, so just make them conditional. - */ - -#define as_log_create(subsystem, category) ({ \ -os_log_t __val; \ -if (AS_AVAILABLE_IOS_TVOS(9, 9)) { \ - __val = os_log_create(subsystem, category); \ -} else { \ - __val = (os_log_t)0; \ -} \ -__val; \ -}) - -#define as_log_debug(log, format, ...) \ -if (AS_AVAILABLE_IOS_TVOS(9, 9)) { \ - os_log_debug(log, format, ##__VA_ARGS__); \ -} else { \ - (void)0; \ -} \ - -#define as_log_info(log, format, ...) \ -if (AS_AVAILABLE_IOS_TVOS(9, 9)) { \ - os_log_info(log, format, ##__VA_ARGS__); \ -} else { \ - (void)0; \ -} \ - -#define as_log_error(log, format, ...) \ -if (AS_AVAILABLE_IOS_TVOS(9, 9)) { \ - os_log_error(log, format, ##__VA_ARGS__); \ -} else { \ - (void)0; \ -} \ - -#define as_log_fault(log, format, ...) \ -if (AS_AVAILABLE_IOS_TVOS(9, 9)) { \ - os_log_fault(log, format, ##__VA_ARGS__); \ -} else { \ - (void)0; \ -} \ - -#if ASEnableVerboseLogging - #define as_log_verbose(log, format, ...) as_log_debug(log, format, ##__VA_ARGS__) -#else - #define as_log_verbose(log, format, ...) -#endif diff --git a/submodules/AsyncDisplayKit/Source/Base/ASLog.mm b/submodules/AsyncDisplayKit/Source/Base/ASLog.mm deleted file mode 100644 index 270246454b..0000000000 --- a/submodules/AsyncDisplayKit/Source/Base/ASLog.mm +++ /dev/null @@ -1,48 +0,0 @@ -// -// ASLog.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -static atomic_bool __ASLogEnabled = ATOMIC_VAR_INIT(YES); - -void ASDisableLogging() { - atomic_store(&__ASLogEnabled, NO); -} - -void ASEnableLogging() { - atomic_store(&__ASLogEnabled, YES); -} - -ASDISPLAYNODE_INLINE BOOL ASLoggingIsEnabled() { - return atomic_load(&__ASLogEnabled); -} - -os_log_t ASNodeLog() { - return (ASNodeLogEnabled && ASLoggingIsEnabled()) ? ASCreateOnce(as_log_create("org.TextureGroup.Texture", "Node")) : OS_LOG_DISABLED; -} - -os_log_t ASLayoutLog() { - return (ASLayoutLogEnabled && ASLoggingIsEnabled()) ? ASCreateOnce(as_log_create("org.TextureGroup.Texture", "Layout")) : OS_LOG_DISABLED; -} - -os_log_t ASCollectionLog() { - return (ASCollectionLogEnabled && ASLoggingIsEnabled()) ?ASCreateOnce(as_log_create("org.TextureGroup.Texture", "Collection")) : OS_LOG_DISABLED; -} - -os_log_t ASDisplayLog() { - return (ASDisplayLogEnabled && ASLoggingIsEnabled()) ?ASCreateOnce(as_log_create("org.TextureGroup.Texture", "Display")) : OS_LOG_DISABLED; -} - -os_log_t ASImageLoadingLog() { - return (ASImageLoadingLogEnabled && ASLoggingIsEnabled()) ? ASCreateOnce(as_log_create("org.TextureGroup.Texture", "ImageLoading")) : OS_LOG_DISABLED; -} - -os_log_t ASMainThreadDeallocationLog() { - return (ASMainThreadDeallocationLogEnabled && ASLoggingIsEnabled()) ? ASCreateOnce(as_log_create("org.TextureGroup.Texture", "MainDealloc")) : OS_LOG_DISABLED; -} diff --git a/submodules/AsyncDisplayKit/Source/Debug/AsyncDisplayKit+Debug.h b/submodules/AsyncDisplayKit/Source/Debug/AsyncDisplayKit+Debug.h deleted file mode 100644 index 2f0826289a..0000000000 --- a/submodules/AsyncDisplayKit/Source/Debug/AsyncDisplayKit+Debug.h +++ /dev/null @@ -1,55 +0,0 @@ -// -// AsyncDisplayKit+Debug.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface ASImageNode (Debugging) - -/** - * Enables an ASImageNode debug label that shows the ratio of pixels in the source image to those in - * the displayed bounds (including cropRect). This helps detect excessive image fetching / downscaling, - * as well as upscaling (such as providing a URL not suitable for a Retina device). For dev purposes only. - * Specify YES to show the label on all ASImageNodes with non-1.0x source-to-bounds pixel ratio. - */ -@property (class, nonatomic) BOOL shouldShowImageScalingOverlay; - -@end - -@interface ASControlNode (Debugging) - -/** - * Class method to enable a visualization overlay of the tappable area on the ASControlNode. For app debugging purposes only. - * NOTE: GESTURE RECOGNIZERS, (including tap gesture recognizers on a control node) WILL NOT BE VISUALIZED!!! - * Overlay = translucent GREEN color, - * edges that are clipped by the tappable area of any parent (their bounds + hitTestSlop) in the hierarchy = DARK GREEN BORDERED EDGE, - * edges that are clipped by clipToBounds = YES of any parent in the hierarchy = ORANGE BORDERED EDGE (may still receive touches beyond - * overlay rect, but can't be visualized). - * Specify YES to make this debug feature enabled when messaging the ASControlNode class. - */ -@property (class, nonatomic) BOOL enableHitTestDebug; - -@end - -#ifndef MINIMAL_ASDK -@interface ASDisplayNode (RangeDebugging) - -/** - * Enable a visualization overlay of the all table/collection tuning parameters. For dev purposes only. - * To use, set this in the AppDelegate --> ASDisplayNode.shouldShowRangeDebugOverlay = YES - */ -@property (class, nonatomic) BOOL shouldShowRangeDebugOverlay; - -@end -#endif - - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Debug/AsyncDisplayKit+Debug.mm b/submodules/AsyncDisplayKit/Source/Debug/AsyncDisplayKit+Debug.mm deleted file mode 100644 index f527098dcc..0000000000 --- a/submodules/AsyncDisplayKit/Source/Debug/AsyncDisplayKit+Debug.mm +++ /dev/null @@ -1,758 +0,0 @@ -// -// AsyncDisplayKit+Debug.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - - -#pragma mark - ASImageNode (Debugging) - -static BOOL __shouldShowImageScalingOverlay = NO; - -@implementation ASImageNode (Debugging) - -+ (void)setShouldShowImageScalingOverlay:(BOOL)show; -{ - __shouldShowImageScalingOverlay = show; -} - -+ (BOOL)shouldShowImageScalingOverlay -{ - return __shouldShowImageScalingOverlay; -} - -@end - -#pragma mark - ASControlNode (DebuggingInternal) - -static BOOL __enableHitTestDebug = NO; - -@interface ASControlNode (DebuggingInternal) - -- (ASImageNode *)debugHighlightOverlay; - -@end - -@implementation ASControlNode (Debugging) - -+ (void)setEnableHitTestDebug:(BOOL)enable -{ - __enableHitTestDebug = enable; -} - -+ (BOOL)enableHitTestDebug -{ - return __enableHitTestDebug; -} - -// layout method required ONLY when hitTestDebug is enabled -- (void)layout -{ - [super layout]; - - if ([ASControlNode enableHitTestDebug]) { - - // Construct hitTestDebug highlight overlay frame indicating tappable area of a node, which can be restricted by two things: - - // (1) Any parent's tapable area (its own bounds + hitTestSlop) may restrict the desired tappable area expansion using - // hitTestSlop of a child as UIKit event delivery (hitTest:) will not search sub-hierarchies if one of our parents does - // not return YES for pointInside:. To circumvent this restriction, a developer will need to set / adjust the hitTestSlop - // on the limiting parent. This is indicated in the overlay by a dark GREEN edge. This is an ACTUAL restriction. - - // (2) Any parent's .clipToBounds. If a parent is clipping, we cannot show the overlay outside that area - // (although it still respond to touch). To indicate that the overlay cannot accurately display the true tappable area, - // the overlay will have an ORANGE edge. This is a VISUALIZATION restriction. - - CGRect intersectRect = UIEdgeInsetsInsetRect(self.bounds, [self hitTestSlop]); - UIRectEdge clippedEdges = UIRectEdgeNone; - UIRectEdge clipsToBoundsClippedEdges = UIRectEdgeNone; - CALayer *layer = self.layer; - CALayer *intersectLayer = layer; - CALayer *intersectSuperlayer = layer.superlayer; - - // FIXED: Stop climbing hierarchy if UIScrollView is encountered (its offset bounds origin may make it seem like our events - // will be clipped when scrolling will actually reveal them (because this process will not re-run due to scrolling)) - while (intersectSuperlayer && ![intersectSuperlayer.delegate respondsToSelector:@selector(contentOffset)]) { - - // Get parent's tappable area - CGRect parentHitRect = intersectSuperlayer.bounds; - BOOL parentClipsToBounds = NO; - - // If parent is a node, tappable area may be expanded by hitTestSlop - ASDisplayNode *parentNode = ASLayerToDisplayNode(intersectSuperlayer); - if (parentNode) { - UIEdgeInsets parentSlop = [parentNode hitTestSlop]; - - // If parent has hitTestSlop, expand tappable area (if parent doesn't clipToBounds) - if (!UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, parentSlop)) { - parentClipsToBounds = parentNode.clipsToBounds; - if (!parentClipsToBounds) { - parentHitRect = UIEdgeInsetsInsetRect(parentHitRect, [parentNode hitTestSlop]); - } - } - } - - // Convert our current rect to parent coordinates - CGRect intersectRectInParentCoordinates = [intersectSuperlayer convertRect:intersectRect fromLayer:intersectLayer]; - - // Intersect rect with the parent's tappable area rect - intersectRect = CGRectIntersection(parentHitRect, intersectRectInParentCoordinates); - if (!CGSizeEqualToSize(parentHitRect.size, intersectRectInParentCoordinates.size)) { - clippedEdges = [self setEdgesOfIntersectionForChildRect:intersectRectInParentCoordinates - parentRect:parentHitRect rectEdge:clippedEdges]; - if (parentClipsToBounds) { - clipsToBoundsClippedEdges = [self setEdgesOfIntersectionForChildRect:intersectRectInParentCoordinates - parentRect:parentHitRect rectEdge:clipsToBoundsClippedEdges]; - } - } - - // move up hierarchy - intersectLayer = intersectSuperlayer; - intersectSuperlayer = intersectLayer.superlayer; - } - - // produce final overlay image (or fill background if edges aren't restricted) - CGRect finalRect = [intersectLayer convertRect:intersectRect toLayer:layer]; - UIColor *fillColor = [[UIColor greenColor] colorWithAlphaComponent:0.4]; - - ASImageNode *debugOverlay = [self debugHighlightOverlay]; - - // determine if edges are clipped and if so, highlight the restricted edges - if (clippedEdges == UIRectEdgeNone) { - debugOverlay.backgroundColor = fillColor; - } else { - const CGFloat borderWidth = 2.0; - UIColor *borderColor = [[UIColor orangeColor] colorWithAlphaComponent:0.8]; - UIColor *clipsBorderColor = [UIColor colorWithRed:30/255.0 green:90/255.0 blue:50/255.0 alpha:0.7]; - CGRect imgRect = CGRectMake(0, 0, 2.0 * borderWidth + 1.0, 2.0 * borderWidth + 1.0); - - ASGraphicsBeginImageContextWithOptions(imgRect.size, NO, 1); - - [fillColor setFill]; - UIRectFill(imgRect); - - [self drawEdgeIfClippedWithEdges:clippedEdges color:clipsBorderColor borderWidth:borderWidth imgRect:imgRect]; - [self drawEdgeIfClippedWithEdges:clipsToBoundsClippedEdges color:borderColor borderWidth:borderWidth imgRect:imgRect]; - - UIImage *debugHighlightImage = ASGraphicsGetImageAndEndCurrentContext(); - - UIEdgeInsets edgeInsets = UIEdgeInsetsMake(borderWidth, borderWidth, borderWidth, borderWidth); - debugOverlay.image = [debugHighlightImage resizableImageWithCapInsets:edgeInsets resizingMode:UIImageResizingModeStretch]; - debugOverlay.backgroundColor = nil; - } - - debugOverlay.frame = finalRect; - } -} - -- (UIRectEdge)setEdgesOfIntersectionForChildRect:(CGRect)childRect parentRect:(CGRect)parentRect rectEdge:(UIRectEdge)rectEdge -{ - // determine which edges of childRect are outside parentRect (and thus will be clipped) - if (childRect.origin.y < parentRect.origin.y) { - rectEdge |= UIRectEdgeTop; - } - if (childRect.origin.x < parentRect.origin.x) { - rectEdge |= UIRectEdgeLeft; - } - if (CGRectGetMaxY(childRect) > CGRectGetMaxY(parentRect)) { - rectEdge |= UIRectEdgeBottom; - } - if (CGRectGetMaxX(childRect) > CGRectGetMaxX(parentRect)) { - rectEdge |= UIRectEdgeRight; - } - - return rectEdge; -} - -- (void)drawEdgeIfClippedWithEdges:(UIRectEdge)rectEdge color:(UIColor *)color borderWidth:(CGFloat)borderWidth imgRect:(CGRect)imgRect -{ - [color setFill]; - - // highlight individual edges of overlay if edge is restricted by parentRect - // so that the developer is aware that increasing hitTestSlop will not result in an expanded tappable area - if (rectEdge & UIRectEdgeTop) { - UIRectFill(CGRectMake(0.0, 0.0, imgRect.size.width, borderWidth)); - } - if (rectEdge & UIRectEdgeLeft) { - UIRectFill(CGRectMake(0.0, 0.0, borderWidth, imgRect.size.height)); - } - if (rectEdge & UIRectEdgeBottom) { - UIRectFill(CGRectMake(0.0, imgRect.size.height - borderWidth, imgRect.size.width, borderWidth)); - } - if (rectEdge & UIRectEdgeRight) { - UIRectFill(CGRectMake(imgRect.size.width - borderWidth, 0.0, borderWidth, imgRect.size.height)); - } -} - -@end - -#pragma mark - ASRangeController (Debugging) - -#ifndef MINIMAL_ASDK - -@interface _ASRangeDebugOverlayView : UIView - -+ (instancetype)sharedInstance NS_RETURNS_RETAINED; - -- (void)addRangeController:(ASRangeController *)rangeController; - -- (void)updateRangeController:(ASRangeController *)controller - withScrollableDirections:(ASScrollDirection)scrollableDirections - scrollDirection:(ASScrollDirection)direction - rangeMode:(ASLayoutRangeMode)mode - displayTuningParameters:(ASRangeTuningParameters)displayTuningParameters - preloadTuningParameters:(ASRangeTuningParameters)preloadTuningParameters - interfaceState:(ASInterfaceState)interfaceState; - -@end - -@interface _ASRangeDebugBarView : UIView - -@property (nonatomic, weak) ASRangeController *rangeController; -@property (nonatomic) BOOL destroyOnLayout; -@property (nonatomic) NSString *debugString; - -- (instancetype)initWithRangeController:(ASRangeController *)rangeController; - -- (void)updateWithVisibleRatio:(CGFloat)visibleRatio - displayRatio:(CGFloat)displayRatio - leadingDisplayRatio:(CGFloat)leadingDisplayRatio - preloadRatio:(CGFloat)preloadRatio - leadingpreloadRatio:(CGFloat)leadingpreloadRatio - direction:(ASScrollDirection)direction; - -@end - -static BOOL __shouldShowRangeDebugOverlay = NO; - -@implementation ASDisplayNode (RangeDebugging) - -+ (void)setShouldShowRangeDebugOverlay:(BOOL)show -{ - __shouldShowRangeDebugOverlay = show; -} - -+ (BOOL)shouldShowRangeDebugOverlay -{ - return __shouldShowRangeDebugOverlay; -} - -@end - -@implementation ASRangeController (DebugInternal) - -+ (void)layoutDebugOverlayIfNeeded -{ - [[_ASRangeDebugOverlayView sharedInstance] setNeedsLayout]; -} - -- (void)addRangeControllerToRangeDebugOverlay -{ - [[_ASRangeDebugOverlayView sharedInstance] addRangeController:self]; -} - -- (void)updateRangeController:(ASRangeController *)controller - withScrollableDirections:(ASScrollDirection)scrollableDirections - scrollDirection:(ASScrollDirection)direction - rangeMode:(ASLayoutRangeMode)mode - displayTuningParameters:(ASRangeTuningParameters)displayTuningParameters - preloadTuningParameters:(ASRangeTuningParameters)preloadTuningParameters - interfaceState:(ASInterfaceState)interfaceState -{ - [[_ASRangeDebugOverlayView sharedInstance] updateRangeController:controller - withScrollableDirections:scrollableDirections - scrollDirection:direction - rangeMode:mode - displayTuningParameters:displayTuningParameters - preloadTuningParameters:preloadTuningParameters - interfaceState:interfaceState]; -} - -@end - - -#pragma mark _ASRangeDebugOverlayView - -@interface _ASRangeDebugOverlayView () -@end - -@implementation _ASRangeDebugOverlayView -{ - NSMutableArray *_rangeControllerViews; - NSInteger _newControllerCount; - NSInteger _removeControllerCount; - BOOL _animating; -} - -+ (UIWindow *)keyWindow -{ - // hack to work around app extensions not having UIApplication...not sure of a better way to do this? - return [[NSClassFromString(@"UIApplication") sharedApplication] keyWindow]; -} - -+ (_ASRangeDebugOverlayView *)sharedInstance NS_RETURNS_RETAINED -{ - static _ASRangeDebugOverlayView *__rangeDebugOverlay = nil; - - if (!__rangeDebugOverlay && ASDisplayNode.shouldShowRangeDebugOverlay) { - __rangeDebugOverlay = [[self alloc] initWithFrame:CGRectZero]; - [[self keyWindow] addSubview:__rangeDebugOverlay]; - } - - return __rangeDebugOverlay; -} - -#define OVERLAY_INSET 10 -#define OVERLAY_SCALE 3 -- (instancetype)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - - if (self) { - _rangeControllerViews = [[NSMutableArray alloc] init]; - self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.4]; - self.layer.zPosition = 1000; - self.clipsToBounds = YES; - - CGSize windowSize = [[[self class] keyWindow] bounds].size; - self.frame = CGRectMake(windowSize.width - (windowSize.width / OVERLAY_SCALE) - OVERLAY_INSET, windowSize.height - OVERLAY_INSET, - windowSize.width / OVERLAY_SCALE, 0.0); - - UIPanGestureRecognizer *panGR = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(rangeDebugOverlayWasPanned:)]; - [self addGestureRecognizer:panGR]; - } - - return self; -} - -#define BAR_THICKNESS 24 - -- (void)layoutSubviews -{ - [super layoutSubviews]; - [UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ - [self layoutToFitAllBarsExcept:0]; - } completion:^(BOOL finished) { - - }]; -} - -- (void)layoutToFitAllBarsExcept:(NSInteger)barsToClip -{ - CGSize boundsSize = self.bounds.size; - CGFloat totalHeight = 0.0; - - CGRect barRect = CGRectMake(0, boundsSize.height - BAR_THICKNESS, self.bounds.size.width, BAR_THICKNESS); - NSMutableArray *displayedBars = [NSMutableArray array]; - - for (_ASRangeDebugBarView *barView in [_rangeControllerViews copy]) { - barView.frame = barRect; - - ASInterfaceState interfaceState = [barView.rangeController.dataSource interfaceStateForRangeController:barView.rangeController]; - - if (!(interfaceState & (ASInterfaceStateVisible))) { - if (barView.destroyOnLayout && barView.alpha == 0.0) { - [_rangeControllerViews removeObjectIdenticalTo:barView]; - [barView removeFromSuperview]; - } else { - barView.alpha = 0.0; - } - } else { - assert(!barView.destroyOnLayout); // In this case we should not have a visible interfaceState - barView.alpha = 1.0; - totalHeight += BAR_THICKNESS; - barRect.origin.y -= BAR_THICKNESS; - [displayedBars addObject:barView]; - } - } - - if (totalHeight > 0) { - totalHeight -= (BAR_THICKNESS * barsToClip); - } - - if (barsToClip == 0) { - CGRect overlayFrame = self.frame; - CGFloat heightChange = (overlayFrame.size.height - totalHeight); - - overlayFrame.origin.y += heightChange; - overlayFrame.size.height = totalHeight; - self.frame = overlayFrame; - - for (_ASRangeDebugBarView *barView in displayedBars) { - [self offsetYOrigin:-heightChange forView:barView]; - } - } -} - -- (void)setOrigin:(CGPoint)origin forView:(UIView *)view -{ - CGRect newFrame = view.frame; - newFrame.origin = origin; - view.frame = newFrame; -} - -- (void)offsetYOrigin:(CGFloat)offset forView:(UIView *)view -{ - CGRect newFrame = view.frame; - newFrame.origin = CGPointMake(newFrame.origin.x, newFrame.origin.y + offset); - view.frame = newFrame; -} - -- (void)addRangeController:(ASRangeController *)rangeController -{ - for (_ASRangeDebugBarView *rangeView in _rangeControllerViews) { - if (rangeView.rangeController == rangeController) { - return; - } - } - _ASRangeDebugBarView *rangeView = [[_ASRangeDebugBarView alloc] initWithRangeController:rangeController]; - [_rangeControllerViews addObject:rangeView]; - [self addSubview:rangeView]; - - if (!_animating) { - [self layoutToFitAllBarsExcept:1]; - } - - [UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ - _animating = YES; - [self layoutToFitAllBarsExcept:0]; - } completion:^(BOOL finished) { - _animating = NO; - }]; -} - -- (void)updateRangeController:(ASRangeController *)controller - withScrollableDirections:(ASScrollDirection)scrollableDirections - scrollDirection:(ASScrollDirection)scrollDirection - rangeMode:(ASLayoutRangeMode)rangeMode - displayTuningParameters:(ASRangeTuningParameters)displayTuningParameters - preloadTuningParameters:(ASRangeTuningParameters)preloadTuningParameters - interfaceState:(ASInterfaceState)interfaceState; -{ - _ASRangeDebugBarView *viewToUpdate = [self barViewForRangeController:controller]; - - CGRect boundsRect = self.bounds; - CGRect visibleRect = CGRectExpandToRangeWithScrollableDirections(boundsRect, ASRangeTuningParametersZero, scrollableDirections, scrollDirection); - CGRect displayRect = CGRectExpandToRangeWithScrollableDirections(boundsRect, displayTuningParameters, scrollableDirections, scrollDirection); - CGRect preloadRect = CGRectExpandToRangeWithScrollableDirections(boundsRect, preloadTuningParameters, scrollableDirections, scrollDirection); - - // figure out which is biggest and assume that is full bounds - BOOL displayRangeLargerThanPreload = NO; - CGFloat visibleRatio = 0; - CGFloat displayRatio = 0; - CGFloat preloadRatio = 0; - CGFloat leadingDisplayTuningRatio = 0; - CGFloat leadingPreloadTuningRatio = 0; - - if (!((displayTuningParameters.leadingBufferScreenfuls + displayTuningParameters.trailingBufferScreenfuls) == 0)) { - leadingDisplayTuningRatio = displayTuningParameters.leadingBufferScreenfuls / (displayTuningParameters.leadingBufferScreenfuls + displayTuningParameters.trailingBufferScreenfuls); - } - if (!((preloadTuningParameters.leadingBufferScreenfuls + preloadTuningParameters.trailingBufferScreenfuls) == 0)) { - leadingPreloadTuningRatio = preloadTuningParameters.leadingBufferScreenfuls / (preloadTuningParameters.leadingBufferScreenfuls + preloadTuningParameters.trailingBufferScreenfuls); - } - - if (ASScrollDirectionContainsVerticalDirection(scrollDirection)) { - - if (displayRect.size.height >= preloadRect.size.height) { - displayRangeLargerThanPreload = YES; - } else { - displayRangeLargerThanPreload = NO; - } - - if (displayRangeLargerThanPreload) { - visibleRatio = visibleRect.size.height / displayRect.size.height; - displayRatio = 1.0; - preloadRatio = preloadRect.size.height / displayRect.size.height; - } else { - visibleRatio = visibleRect.size.height / preloadRect.size.height; - displayRatio = displayRect.size.height / preloadRect.size.height; - preloadRatio = 1.0; - } - - } else { - - if (displayRect.size.width >= preloadRect.size.width) { - displayRangeLargerThanPreload = YES; - } else { - displayRangeLargerThanPreload = NO; - } - - if (displayRangeLargerThanPreload) { - visibleRatio = visibleRect.size.width / displayRect.size.width; - displayRatio = 1.0; - preloadRatio = preloadRect.size.width / displayRect.size.width; - } else { - visibleRatio = visibleRect.size.width / preloadRect.size.width; - displayRatio = displayRect.size.width / preloadRect.size.width; - preloadRatio = 1.0; - } - } - - [viewToUpdate updateWithVisibleRatio:visibleRatio - displayRatio:displayRatio - leadingDisplayRatio:leadingDisplayTuningRatio - preloadRatio:preloadRatio - leadingpreloadRatio:leadingPreloadTuningRatio - direction:scrollDirection]; - - [self setNeedsLayout]; -} - -- (_ASRangeDebugBarView *)barViewForRangeController:(ASRangeController *)controller -{ - _ASRangeDebugBarView *rangeControllerBarView = nil; - - for (_ASRangeDebugBarView *rangeView in [[_rangeControllerViews reverseObjectEnumerator] allObjects]) { - // remove barView if its rangeController has been deleted - if (!rangeView.rangeController) { - rangeView.destroyOnLayout = YES; - [self setNeedsLayout]; - } - ASInterfaceState interfaceState = [rangeView.rangeController.dataSource interfaceStateForRangeController:rangeView.rangeController]; - if (!(interfaceState & (ASInterfaceStateVisible | ASInterfaceStateDisplay))) { - [self setNeedsLayout]; - } - - if ([rangeView.rangeController isEqual:controller]) { - rangeControllerBarView = rangeView; - } - } - - return rangeControllerBarView; -} - -#define MIN_VISIBLE_INSET 40 -- (void)rangeDebugOverlayWasPanned:(UIPanGestureRecognizer *)recognizer -{ - CGPoint translation = [recognizer translationInView:recognizer.view]; - CGFloat newCenterX = recognizer.view.center.x + translation.x; - CGFloat newCenterY = recognizer.view.center.y + translation.y; - CGSize boundsSize = recognizer.view.bounds.size; - CGSize superBoundsSize = recognizer.view.superview.bounds.size; - CGFloat minAllowableX = -boundsSize.width / 2.0 + MIN_VISIBLE_INSET; - CGFloat maxAllowableX = superBoundsSize.width + boundsSize.width / 2.0 - MIN_VISIBLE_INSET; - - if (newCenterX > maxAllowableX) { - newCenterX = maxAllowableX; - } else if (newCenterX < minAllowableX) { - newCenterX = minAllowableX; - } - - CGFloat minAllowableY = -boundsSize.height / 2.0 + MIN_VISIBLE_INSET; - CGFloat maxAllowableY = superBoundsSize.height + boundsSize.height / 2.0 - MIN_VISIBLE_INSET; - - if (newCenterY > maxAllowableY) { - newCenterY = maxAllowableY; - } else if (newCenterY < minAllowableY) { - newCenterY = minAllowableY; - } - - recognizer.view.center = CGPointMake(newCenterX, newCenterY); - [recognizer setTranslation:CGPointMake(0, 0) inView:recognizer.view]; -} - -@end - -#pragma mark _ASRangeDebugBarView - -@implementation _ASRangeDebugBarView -{ - ASTextNode *_debugText; - ASTextNode *_leftDebugText; - ASTextNode *_rightDebugText; - ASImageNode *_visibleRect; - ASImageNode *_displayRect; - ASImageNode *_preloadRect; - CGFloat _visibleRatio; - CGFloat _displayRatio; - CGFloat _preloadRatio; - CGFloat _leadingDisplayRatio; - CGFloat _leadingpreloadRatio; - ASScrollDirection _scrollDirection; - BOOL _firstLayoutOfRects; -} - -- (instancetype)initWithRangeController:(ASRangeController *)rangeController -{ - self = [super initWithFrame:CGRectZero]; - if (self) { - _firstLayoutOfRects = YES; - _rangeController = rangeController; - _debugText = [self createDebugTextNode]; - _leftDebugText = [self createDebugTextNode]; - _rightDebugText = [self createDebugTextNode]; - _preloadRect = [self createRangeNodeWithColor:[UIColor orangeColor]]; - _displayRect = [self createRangeNodeWithColor:[UIColor yellowColor]]; - _visibleRect = [self createRangeNodeWithColor:[UIColor greenColor]]; - } - - return self; -} - -#define HORIZONTAL_INSET 10 -- (void)layoutSubviews -{ - [super layoutSubviews]; - - CGSize boundsSize = self.bounds.size; - CGFloat subCellHeight = 9.0; - [self setBarDebugLabelsWithSize:subCellHeight]; - [self setBarSubviewOrder]; - - CGRect rect = CGRectIntegral(CGRectMake(0, 0, boundsSize.width, floorf(boundsSize.height / 2.0))); - rect.size = [_debugText layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))].size; - rect.origin.x = (boundsSize.width - rect.size.width) / 2.0; - _debugText.frame = rect; - rect.origin.y += rect.size.height; - - rect.origin.x = 0; - rect.size = CGSizeMake(HORIZONTAL_INSET, boundsSize.height / 2.0); - _leftDebugText.frame = rect; - - rect.origin.x = boundsSize.width - HORIZONTAL_INSET; - _rightDebugText.frame = rect; - - CGFloat visibleDimension = (boundsSize.width - 2 * HORIZONTAL_INSET) * _visibleRatio; - CGFloat displayDimension = (boundsSize.width - 2 * HORIZONTAL_INSET) * _displayRatio; - CGFloat preloadDimension = (boundsSize.width - 2 * HORIZONTAL_INSET) * _preloadRatio; - CGFloat visiblePoint = 0; - CGFloat displayPoint = 0; - CGFloat preloadPoint = 0; - - BOOL displayLargerThanPreload = (_displayRatio == 1.0) ? YES : NO; - - if (ASScrollDirectionContainsLeft(_scrollDirection) || ASScrollDirectionContainsUp(_scrollDirection)) { - - if (displayLargerThanPreload) { - visiblePoint = (displayDimension - visibleDimension) * _leadingDisplayRatio; - preloadPoint = visiblePoint - (preloadDimension - visibleDimension) * _leadingpreloadRatio; - } else { - visiblePoint = (preloadDimension - visibleDimension) * _leadingpreloadRatio; - displayPoint = visiblePoint - (displayDimension - visibleDimension) * _leadingDisplayRatio; - } - } else if (ASScrollDirectionContainsRight(_scrollDirection) || ASScrollDirectionContainsDown(_scrollDirection)) { - - if (displayLargerThanPreload) { - visiblePoint = (displayDimension - visibleDimension) * (1 - _leadingDisplayRatio); - preloadPoint = visiblePoint - (preloadDimension - visibleDimension) * (1 - _leadingpreloadRatio); - } else { - visiblePoint = (preloadDimension - visibleDimension) * (1 - _leadingpreloadRatio); - displayPoint = visiblePoint - (displayDimension - visibleDimension) * (1 - _leadingDisplayRatio); - } - } - - BOOL animate = !_firstLayoutOfRects; - [UIView animateWithDuration:animate ? 0.3 : 0.0 delay:0.0 options:UIViewAnimationOptionLayoutSubviews animations:^{ - _visibleRect.frame = CGRectMake(HORIZONTAL_INSET + visiblePoint, rect.origin.y, visibleDimension, subCellHeight); - _displayRect.frame = CGRectMake(HORIZONTAL_INSET + displayPoint, rect.origin.y, displayDimension, subCellHeight); - _preloadRect.frame = CGRectMake(HORIZONTAL_INSET + preloadPoint, rect.origin.y, preloadDimension, subCellHeight); - } completion:^(BOOL finished) {}]; - - if (!animate) { - _visibleRect.alpha = _displayRect.alpha = _preloadRect.alpha = 0; - [UIView animateWithDuration:0.3 animations:^{ - _visibleRect.alpha = _displayRect.alpha = _preloadRect.alpha = 1; - }]; - } - - _firstLayoutOfRects = NO; -} - -- (void)updateWithVisibleRatio:(CGFloat)visibleRatio - displayRatio:(CGFloat)displayRatio - leadingDisplayRatio:(CGFloat)leadingDisplayRatio - preloadRatio:(CGFloat)preloadRatio - leadingpreloadRatio:(CGFloat)leadingpreloadRatio - direction:(ASScrollDirection)scrollDirection -{ - _visibleRatio = visibleRatio; - _displayRatio = displayRatio; - _leadingDisplayRatio = leadingDisplayRatio; - _preloadRatio = preloadRatio; - _leadingpreloadRatio = leadingpreloadRatio; - _scrollDirection = scrollDirection; - - [self setNeedsLayout]; -} - -- (void)setBarSubviewOrder -{ - if (_preloadRatio == 1.0) { - [self sendSubviewToBack:_preloadRect.view]; - } else { - [self sendSubviewToBack:_displayRect.view]; - } - - [self bringSubviewToFront:_visibleRect.view]; -} - -- (void)setBarDebugLabelsWithSize:(CGFloat)size -{ - if (!_debugString) { - _debugString = [[_rangeController dataSource] nameForRangeControllerDataSource]; - } - if (_debugString) { - _debugText.attributedText = [_ASRangeDebugBarView whiteAttributedStringFromString:_debugString withSize:size]; - } - - if (ASScrollDirectionContainsVerticalDirection(_scrollDirection)) { - _leftDebugText.attributedText = [_ASRangeDebugBarView whiteAttributedStringFromString:@"▲" withSize:size]; - _rightDebugText.attributedText = [_ASRangeDebugBarView whiteAttributedStringFromString:@"▼" withSize:size]; - } else if (ASScrollDirectionContainsHorizontalDirection(_scrollDirection)) { - _leftDebugText.attributedText = [_ASRangeDebugBarView whiteAttributedStringFromString:@"◀︎" withSize:size]; - _rightDebugText.attributedText = [_ASRangeDebugBarView whiteAttributedStringFromString:@"▶︎" withSize:size]; - } - - _leftDebugText.hidden = (_scrollDirection != ASScrollDirectionLeft && _scrollDirection != ASScrollDirectionUp); - _rightDebugText.hidden = (_scrollDirection != ASScrollDirectionRight && _scrollDirection != ASScrollDirectionDown); -} - -- (ASTextNode *)createDebugTextNode -{ - ASTextNode *label = [[ASTextNode alloc] init]; - [self addSubnode:label]; - return label; -} - -#define RANGE_BAR_CORNER_RADIUS 3 -#define RANGE_BAR_BORDER_WIDTH 1 -- (ASImageNode *)createRangeNodeWithColor:(UIColor *)color -{ - ASImageNode *rangeBarImageNode = [[ASImageNode alloc] init]; - rangeBarImageNode.image = [UIImage as_resizableRoundedImageWithCornerRadius:RANGE_BAR_CORNER_RADIUS - cornerColor:[UIColor clearColor] - fillColor:[color colorWithAlphaComponent:0.5] - borderColor:[[UIColor blackColor] colorWithAlphaComponent:0.9] - borderWidth:RANGE_BAR_BORDER_WIDTH - roundedCorners:UIRectCornerAllCorners - scale:[[UIScreen mainScreen] scale]]; - [self addSubnode:rangeBarImageNode]; - - return rangeBarImageNode; -} - -+ (NSAttributedString *)whiteAttributedStringFromString:(NSString *)string withSize:(CGFloat)size NS_RETURNS_RETAINED -{ - NSDictionary *attributes = @{NSForegroundColorAttributeName : [UIColor whiteColor], - NSFontAttributeName : [UIFont systemFontOfSize:size]}; - return [[NSAttributedString alloc] initWithString:string attributes:attributes]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Debug/AsyncDisplayKit+Tips.h b/submodules/AsyncDisplayKit/Source/Debug/AsyncDisplayKit+Tips.h deleted file mode 100644 index 6232746a57..0000000000 --- a/submodules/AsyncDisplayKit/Source/Debug/AsyncDisplayKit+Tips.h +++ /dev/null @@ -1,43 +0,0 @@ -// -// AsyncDisplayKit+Tips.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -typedef void(^ASTipDisplayBlock)(ASDisplayNode *node, NSString *message); - -/** - * The methods added to ASDisplayNode to control the tips system. - * - * To enable tips, define AS_ENABLE_TIPS=1 (e.g. modify ASBaseDefines.h). - */ -@interface ASDisplayNode (Tips) - -/** - * Whether this class should have tips active. Default YES. - * - * NOTE: This property is for _disabling_ tips on a per-class basis, - * if they become annoying or have false-positives. The tips system - * is completely disabled unless you define AS_ENABLE_TIPS=1. - */ -@property (class) BOOL enableTips; - -/** - * A block to be run on the main thread to show text when a tip is tapped. - * - * If nil, the default, the message is just logged to the console with the - * ancestry of the node. - */ -@property (class, nonatomic, null_resettable) ASTipDisplayBlock tipDisplayBlock; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Debug/AsyncDisplayKit+Tips.mm b/submodules/AsyncDisplayKit/Source/Debug/AsyncDisplayKit+Tips.mm deleted file mode 100644 index f1a3ec8d72..0000000000 --- a/submodules/AsyncDisplayKit/Source/Debug/AsyncDisplayKit+Tips.mm +++ /dev/null @@ -1,48 +0,0 @@ -// -// AsyncDisplayKit+Tips.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -@implementation ASDisplayNode (Tips) - -static char ASDisplayNodeEnableTipsKey; -static ASTipDisplayBlock _Nullable __tipDisplayBlock; - -/** - * Use associated objects with NSNumbers. This is a debug property - simplicity is king. - */ -+ (void)setEnableTips:(BOOL)enableTips -{ - objc_setAssociatedObject(self, &ASDisplayNodeEnableTipsKey, @(enableTips), OBJC_ASSOCIATION_COPY); -} - -+ (BOOL)enableTips -{ - NSNumber *result = objc_getAssociatedObject(self, &ASDisplayNodeEnableTipsKey); - if (result == nil) { - return YES; - } - return result.boolValue; -} - - -+ (void)setTipDisplayBlock:(ASTipDisplayBlock)tipDisplayBlock -{ - __tipDisplayBlock = tipDisplayBlock; -} - -+ (ASTipDisplayBlock)tipDisplayBlock -{ - return __tipDisplayBlock ?: ^(ASDisplayNode *node, NSString *string) { - NSLog(@"%@. Node ancestry: %@", string, node.ancestryDescription); - }; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/Details/ASAbstractLayoutController.h b/submodules/AsyncDisplayKit/Source/Details/ASAbstractLayoutController.h deleted file mode 100644 index 6de7801fb6..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASAbstractLayoutController.h +++ /dev/null @@ -1,33 +0,0 @@ -// -// ASAbstractLayoutController.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -AS_EXTERN ASDirectionalScreenfulBuffer ASDirectionalScreenfulBufferHorizontal(ASScrollDirection scrollDirection, ASRangeTuningParameters rangeTuningParameters); - -AS_EXTERN ASDirectionalScreenfulBuffer ASDirectionalScreenfulBufferVertical(ASScrollDirection scrollDirection, ASRangeTuningParameters rangeTuningParameters); - -AS_EXTERN CGRect CGRectExpandToRangeWithScrollableDirections(CGRect rect, ASRangeTuningParameters tuningParameters, ASScrollDirection scrollableDirections, ASScrollDirection scrollDirection); - -@interface ASAbstractLayoutController : NSObject - -@end - -@interface ASAbstractLayoutController (Unavailable) - -- (NSHashTable *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType __unavailable; - -- (void)allIndexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode displaySet:(NSHashTable * _Nullable * _Nullable)displaySet preloadSet:(NSHashTable * _Nullable * _Nullable)preloadSet __unavailable; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Details/ASAbstractLayoutController.mm b/submodules/AsyncDisplayKit/Source/Details/ASAbstractLayoutController.mm deleted file mode 100644 index 9d6b61f689..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASAbstractLayoutController.mm +++ /dev/null @@ -1,185 +0,0 @@ -// -// ASAbstractLayoutController.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - -ASRangeTuningParameters const ASRangeTuningParametersZero = {}; - -BOOL ASRangeTuningParametersEqualToRangeTuningParameters(ASRangeTuningParameters lhs, ASRangeTuningParameters rhs) -{ - return lhs.leadingBufferScreenfuls == rhs.leadingBufferScreenfuls && lhs.trailingBufferScreenfuls == rhs.trailingBufferScreenfuls; -} - -ASDirectionalScreenfulBuffer ASDirectionalScreenfulBufferHorizontal(ASScrollDirection scrollDirection, - ASRangeTuningParameters rangeTuningParameters) -{ - ASDirectionalScreenfulBuffer horizontalBuffer = {0, 0}; - BOOL movingRight = ASScrollDirectionContainsRight(scrollDirection); - - horizontalBuffer.positiveDirection = movingRight ? rangeTuningParameters.leadingBufferScreenfuls - : rangeTuningParameters.trailingBufferScreenfuls; - horizontalBuffer.negativeDirection = movingRight ? rangeTuningParameters.trailingBufferScreenfuls - : rangeTuningParameters.leadingBufferScreenfuls; - return horizontalBuffer; -} - -ASDirectionalScreenfulBuffer ASDirectionalScreenfulBufferVertical(ASScrollDirection scrollDirection, - ASRangeTuningParameters rangeTuningParameters) -{ - ASDirectionalScreenfulBuffer verticalBuffer = {0, 0}; - BOOL movingDown = ASScrollDirectionContainsDown(scrollDirection); - - verticalBuffer.positiveDirection = movingDown ? rangeTuningParameters.leadingBufferScreenfuls - : rangeTuningParameters.trailingBufferScreenfuls; - verticalBuffer.negativeDirection = movingDown ? rangeTuningParameters.trailingBufferScreenfuls - : rangeTuningParameters.leadingBufferScreenfuls; - return verticalBuffer; -} - -CGRect CGRectExpandHorizontally(CGRect rect, ASDirectionalScreenfulBuffer buffer) -{ - CGFloat negativeDirectionWidth = buffer.negativeDirection * rect.size.width; - CGFloat positiveDirectionWidth = buffer.positiveDirection * rect.size.width; - rect.size.width = negativeDirectionWidth + rect.size.width + positiveDirectionWidth; - rect.origin.x -= negativeDirectionWidth; - return rect; -} - -CGRect CGRectExpandVertically(CGRect rect, ASDirectionalScreenfulBuffer buffer) -{ - CGFloat negativeDirectionHeight = buffer.negativeDirection * rect.size.height; - CGFloat positiveDirectionHeight = buffer.positiveDirection * rect.size.height; - rect.size.height = negativeDirectionHeight + rect.size.height + positiveDirectionHeight; - rect.origin.y -= negativeDirectionHeight; - return rect; -} - -CGRect CGRectExpandToRangeWithScrollableDirections(CGRect rect, ASRangeTuningParameters tuningParameters, - ASScrollDirection scrollableDirections, ASScrollDirection scrollDirection) -{ - // Can scroll horizontally - expand the range appropriately - if (ASScrollDirectionContainsHorizontalDirection(scrollableDirections)) { - ASDirectionalScreenfulBuffer horizontalBuffer = ASDirectionalScreenfulBufferHorizontal(scrollDirection, tuningParameters); - rect = CGRectExpandHorizontally(rect, horizontalBuffer); - } - - // Can scroll vertically - expand the range appropriately - if (ASScrollDirectionContainsVerticalDirection(scrollableDirections)) { - ASDirectionalScreenfulBuffer verticalBuffer = ASDirectionalScreenfulBufferVertical(scrollDirection, tuningParameters); - rect = CGRectExpandVertically(rect, verticalBuffer); - } - - return rect; -} - -@interface ASAbstractLayoutController () { - std::vector> _tuningParameters; -} -@end - -@implementation ASAbstractLayoutController - -+ (std::vector>)defaultTuningParameters -{ - auto tuningParameters = std::vector> (ASLayoutRangeModeCount, std::vector (ASLayoutRangeTypeCount)); - - tuningParameters[ASLayoutRangeModeFull][ASLayoutRangeTypeDisplay] = { - .leadingBufferScreenfuls = 1.0, - .trailingBufferScreenfuls = 0.5 - }; - - tuningParameters[ASLayoutRangeModeFull][ASLayoutRangeTypePreload] = { - .leadingBufferScreenfuls = 2.5, - .trailingBufferScreenfuls = 1.5 - }; - - tuningParameters[ASLayoutRangeModeMinimum][ASLayoutRangeTypeDisplay] = { - .leadingBufferScreenfuls = 0.25, - .trailingBufferScreenfuls = 0.25 - }; - tuningParameters[ASLayoutRangeModeMinimum][ASLayoutRangeTypePreload] = { - .leadingBufferScreenfuls = 0.5, - .trailingBufferScreenfuls = 0.25 - }; - - tuningParameters[ASLayoutRangeModeVisibleOnly][ASLayoutRangeTypeDisplay] = { - .leadingBufferScreenfuls = 0, - .trailingBufferScreenfuls = 0 - }; - tuningParameters[ASLayoutRangeModeVisibleOnly][ASLayoutRangeTypePreload] = { - .leadingBufferScreenfuls = 0, - .trailingBufferScreenfuls = 0 - }; - - // The Low Memory range mode has special handling. Because a zero range still includes the visible area / bounds, - // in order to implement the behavior of releasing all graphics memory (backing stores), ASRangeController must check - // for this range mode and use an empty set for displayIndexPaths rather than querying the ASLayoutController for the indexPaths. - tuningParameters[ASLayoutRangeModeLowMemory][ASLayoutRangeTypeDisplay] = { - .leadingBufferScreenfuls = 0, - .trailingBufferScreenfuls = 0 - }; - tuningParameters[ASLayoutRangeModeLowMemory][ASLayoutRangeTypePreload] = { - .leadingBufferScreenfuls = 0, - .trailingBufferScreenfuls = 0 - }; - return tuningParameters; -} - -- (instancetype)init -{ - if (!(self = [super init])) { - return nil; - } - ASDisplayNodeAssert(self.class != [ASAbstractLayoutController class], @"Should never create instances of abstract class ASAbstractLayoutController."); - - _tuningParameters = [[self class] defaultTuningParameters]; - - return self; -} - -#pragma mark - Tuning Parameters - -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType -{ - return [self tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; -} - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType -{ - return [self setTuningParameters:tuningParameters forRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; -} - -- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - ASDisplayNodeAssert(rangeMode < _tuningParameters.size() && rangeType < _tuningParameters[rangeMode].size(), @"Requesting a range that is OOB for the configured tuning parameters"); - return _tuningParameters[rangeMode][rangeType]; -} - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - ASDisplayNodeAssert(rangeMode < _tuningParameters.size() && rangeType < _tuningParameters[rangeMode].size(), @"Setting a range that is OOB for the configured tuning parameters"); - _tuningParameters[rangeMode][rangeType] = tuningParameters; -} - -#pragma mark - Abstract Index Path Range Support - -- (NSHashTable *)elementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType map:(ASElementMap *)map -{ - ASDisplayNodeAssertNotSupported(); - return nil; -} - -- (void)allElementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode displaySet:(NSHashTable *__autoreleasing _Nullable *)displaySet preloadSet:(NSHashTable *__autoreleasing _Nullable *)preloadSet map:(ASElementMap *)map -{ - ASDisplayNodeAssertNotSupported(); -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/Details/ASBasicImageDownloader.h b/submodules/AsyncDisplayKit/Source/Details/ASBasicImageDownloader.h deleted file mode 100644 index d5f8169745..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASBasicImageDownloader.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// ASBasicImageDownloader.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * @abstract Simple NSURLSession-based image downloader. - */ -@interface ASBasicImageDownloader : NSObject - -/** - * A shared image downloader which can be used by @c ASNetworkImageNodes and @c ASMultiplexImageNodes. - * The userInfo provided by this downloader is `nil`. - * - * This is a very basic image downloader. It does not support caching, progressive downloading and likely - * isn't something you should use in production. If you'd like something production ready, see @c ASPINRemoteImageDownloader - * - * @note It is strongly recommended you include PINRemoteImage and use @c ASPINRemoteImageDownloader instead. - */ -@property (class, readonly) ASBasicImageDownloader *sharedImageDownloader; -+ (ASBasicImageDownloader *)sharedImageDownloader NS_RETURNS_RETAINED; - -+ (instancetype)new __attribute__((unavailable("+[ASBasicImageDownloader sharedImageDownloader] must be used."))); -- (instancetype)init __attribute__((unavailable("+[ASBasicImageDownloader sharedImageDownloader] must be used."))); - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASBasicImageDownloader.mm b/submodules/AsyncDisplayKit/Source/Details/ASBasicImageDownloader.mm deleted file mode 100644 index 17c035a41c..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASBasicImageDownloader.mm +++ /dev/null @@ -1,355 +0,0 @@ -// -// ASBasicImageDownloader.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// -#ifndef MINIMAL_ASDK -#import - -#import -#import - -#import -#import -#import - -using AS::MutexLocker; - -#pragma mark - -/** - * Collection of properties associated with a download request. - */ - -NSString * const kASBasicImageDownloaderContextCallbackQueue = @"kASBasicImageDownloaderContextCallbackQueue"; -NSString * const kASBasicImageDownloaderContextProgressBlock = @"kASBasicImageDownloaderContextProgressBlock"; -NSString * const kASBasicImageDownloaderContextCompletionBlock = @"kASBasicImageDownloaderContextCompletionBlock"; - -static inline float NSURLSessionTaskPriorityWithImageDownloaderPriority(ASImageDownloaderPriority priority) { - switch (priority) { - case ASImageDownloaderPriorityPreload: - return NSURLSessionTaskPriorityLow; - - case ASImageDownloaderPriorityImminent: - return NSURLSessionTaskPriorityDefault; - - case ASImageDownloaderPriorityVisible: - return NSURLSessionTaskPriorityHigh; - } -} - -@interface ASBasicImageDownloaderContext () -{ - BOOL _invalid; - AS::RecursiveMutex __instanceLock__; -} - -@property (nonatomic) NSMutableArray *callbackDatas; - -@end - -@implementation ASBasicImageDownloaderContext - -static NSMutableDictionary *currentRequests = nil; - -+ (AS::Mutex *)currentRequestLock -{ - static dispatch_once_t onceToken; - static AS::Mutex *currentRequestsLock; - dispatch_once(&onceToken, ^{ - currentRequestsLock = new AS::Mutex(); - }); - return currentRequestsLock; -} - -+ (ASBasicImageDownloaderContext *)contextForURL:(NSURL *)URL -{ - MutexLocker l(*self.currentRequestLock); - if (!currentRequests) { - currentRequests = [[NSMutableDictionary alloc] init]; - } - ASBasicImageDownloaderContext *context = currentRequests[URL]; - if (!context) { - context = [[ASBasicImageDownloaderContext alloc] initWithURL:URL]; - currentRequests[URL] = context; - } - return context; -} - -+ (void)cancelContextWithURL:(NSURL *)URL -{ - MutexLocker l(*self.currentRequestLock); - if (currentRequests) { - [currentRequests removeObjectForKey:URL]; - } -} - -- (instancetype)initWithURL:(NSURL *)URL -{ - if (self = [super init]) { - _URL = URL; - _callbackDatas = [NSMutableArray array]; - } - return self; -} - -- (void)cancel -{ - MutexLocker l(__instanceLock__); - - NSURLSessionTask *sessionTask = self.sessionTask; - if (sessionTask) { - [sessionTask cancel]; - self.sessionTask = nil; - } - - _invalid = YES; - [self.class cancelContextWithURL:self.URL]; -} - -- (BOOL)isCancelled -{ - MutexLocker l(__instanceLock__); - return _invalid; -} - -- (void)addCallbackData:(NSDictionary *)callbackData -{ - MutexLocker l(__instanceLock__); - [self.callbackDatas addObject:callbackData]; -} - -- (void)performProgressBlocks:(CGFloat)progress -{ - MutexLocker l(__instanceLock__); - for (NSDictionary *callbackData in self.callbackDatas) { - ASImageDownloaderProgress progressBlock = callbackData[kASBasicImageDownloaderContextProgressBlock]; - dispatch_queue_t callbackQueue = callbackData[kASBasicImageDownloaderContextCallbackQueue]; - - if (progressBlock) { - dispatch_async(callbackQueue, ^{ - progressBlock(progress); - }); - } - } -} - -- (void)completeWithImage:(UIImage *)image error:(NSError *)error -{ - MutexLocker l(__instanceLock__); - for (NSDictionary *callbackData in self.callbackDatas) { - ASImageDownloaderCompletion completionBlock = callbackData[kASBasicImageDownloaderContextCompletionBlock]; - dispatch_queue_t callbackQueue = callbackData[kASBasicImageDownloaderContextCallbackQueue]; - - if (completionBlock) { - dispatch_async(callbackQueue, ^{ - completionBlock(image, error, nil, nil); - }); - } - } - - self.sessionTask = nil; - [self.callbackDatas removeAllObjects]; -} - -- (NSURLSessionTask *)createSessionTaskIfNecessaryWithBlock:(NSURLSessionTask *(^)())creationBlock { - { - MutexLocker l(__instanceLock__); - - if (self.isCancelled) { - return nil; - } - - if (self.sessionTask && (self.sessionTask.state == NSURLSessionTaskStateRunning)) { - return nil; - } - } - - NSURLSessionTask *newTask = creationBlock(); - - { - MutexLocker l(__instanceLock__); - - if (self.isCancelled) { - return nil; - } - - if (self.sessionTask && (self.sessionTask.state == NSURLSessionTaskStateRunning)) { - return nil; - } - - self.sessionTask = newTask; - - return self.sessionTask; - } -} - -@end - - -#pragma mark - -/** - * NSURLSessionDownloadTask lacks a `userInfo` property, so add this association ourselves. - */ -@interface NSURLRequest (ASBasicImageDownloader) -@property (nonatomic) ASBasicImageDownloaderContext *asyncdisplaykit_context; -@end - -@implementation NSURLRequest (ASBasicImageDownloader) - -static const void *ContextKey() { - return @selector(asyncdisplaykit_context); -} - -- (void)setAsyncdisplaykit_context:(ASBasicImageDownloaderContext *)asyncdisplaykit_context -{ - objc_setAssociatedObject(self, ContextKey(), asyncdisplaykit_context, OBJC_ASSOCIATION_RETAIN_NONATOMIC); -} -- (ASBasicImageDownloader *)asyncdisplaykit_context -{ - return objc_getAssociatedObject(self, ContextKey()); -} -@end - - -#pragma mark - -@interface ASBasicImageDownloader () -{ - NSOperationQueue *_sessionDelegateQueue; - NSURLSession *_session; -} - -@end - -@implementation ASBasicImageDownloader - -+ (ASBasicImageDownloader *)sharedImageDownloader -{ - static ASBasicImageDownloader *sharedImageDownloader = nil; - static dispatch_once_t once = 0; - dispatch_once(&once, ^{ - sharedImageDownloader = [[ASBasicImageDownloader alloc] _init]; - }); - return sharedImageDownloader; -} - -#pragma mark Lifecycle. - -- (instancetype)_init -{ - if (!(self = [super init])) - return nil; - - _sessionDelegateQueue = [[NSOperationQueue alloc] init]; - _session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] - delegate:self - delegateQueue:_sessionDelegateQueue]; - - return self; -} - - -#pragma mark ASImageDownloaderProtocol. - -- (nullable id)downloadImageWithURL:(NSURL *)URL - callbackQueue:(dispatch_queue_t)callbackQueue - downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress - completion:(ASImageDownloaderCompletion)completion -{ - return [self downloadImageWithURL:URL - priority:ASImageDownloaderPriorityImminent // maps to default priority - callbackQueue:callbackQueue - downloadProgress:downloadProgress - completion:completion]; -} - -- (nullable id)downloadImageWithURL:(NSURL *)URL - priority:(ASImageDownloaderPriority)priority - callbackQueue:(dispatch_queue_t)callbackQueue - downloadProgress:(ASImageDownloaderProgress)downloadProgress - completion:(ASImageDownloaderCompletion)completion -{ - ASBasicImageDownloaderContext *context = [ASBasicImageDownloaderContext contextForURL:URL]; - - // NSURLSessionDownloadTask will do file I/O to create a temp directory. If called on the main thread this will - // cause significant performance issues. - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - // associate metadata with it - const auto callbackData = [[NSMutableDictionary alloc] init]; - callbackData[kASBasicImageDownloaderContextCallbackQueue] = callbackQueue ? : dispatch_get_main_queue(); - - if (downloadProgress) { - callbackData[kASBasicImageDownloaderContextProgressBlock] = [downloadProgress copy]; - } - - if (completion) { - callbackData[kASBasicImageDownloaderContextCompletionBlock] = [completion copy]; - } - - [context addCallbackData:[[NSDictionary alloc] initWithDictionary:callbackData]]; - - // Create new task if necessary - NSURLSessionDownloadTask *task = (NSURLSessionDownloadTask *)[context createSessionTaskIfNecessaryWithBlock:^(){return [_session downloadTaskWithURL:URL];}]; - - if (task) { - task.priority = NSURLSessionTaskPriorityWithImageDownloaderPriority(priority); - task.originalRequest.asyncdisplaykit_context = context; - - // start downloading - [task resume]; - } - }); - - return context; -} - -- (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier -{ - ASDisplayNodeAssert([downloadIdentifier isKindOfClass:ASBasicImageDownloaderContext.class], @"unexpected downloadIdentifier"); - ASBasicImageDownloaderContext *context = (ASBasicImageDownloaderContext *)downloadIdentifier; - - [context cancel]; -} - - -#pragma mark NSURLSessionDownloadDelegate. - -- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask - didWriteData:(int64_t)bytesWritten - totalBytesWritten:(int64_t)totalBytesWritten - totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite -{ - ASBasicImageDownloaderContext *context = downloadTask.originalRequest.asyncdisplaykit_context; - [context performProgressBlocks:(CGFloat)totalBytesWritten / (CGFloat)totalBytesExpectedToWrite]; -} - -// invoked if the download succeeded with no error -- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask - didFinishDownloadingToURL:(NSURL *)location -{ - ASBasicImageDownloaderContext *context = downloadTask.originalRequest.asyncdisplaykit_context; - if ([context isCancelled]) { - return; - } - - if (context) { - UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:location]]; - [context completeWithImage:image error:nil]; - } -} - -// invoked unconditionally -- (void)URLSession:(NSURLSession *)session task:(NSURLSessionDownloadTask *)task - didCompleteWithError:(NSError *)error -{ - ASBasicImageDownloaderContext *context = task.originalRequest.asyncdisplaykit_context; - if (context && error) { - [context completeWithImage:nil error:error]; - } -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASBatchContext.h b/submodules/AsyncDisplayKit/Source/Details/ASBatchContext.h deleted file mode 100644 index bbe92b8c34..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASBatchContext.h +++ /dev/null @@ -1,69 +0,0 @@ -// -// ASBatchContext.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * @abstract A context object to notify when batch fetches are finished or cancelled. - */ -@interface ASBatchContext : NSObject - -/** - * Retrieve the state of the current batch process. - * - * @return A boolean reflecting if the owner of the context object is fetching another batch. - */ -- (BOOL)isFetching; - -/** - * Let the context object know that a batch fetch was completed. - * - * @param didComplete A boolean that states whether or not the batch fetch completed. - * - * @discussion Only by passing YES will the owner of the context know to attempt another batch update when necessary. - * For instance, when a table has reached the end of its data, a batch fetch will be attempted unless the context - * object thinks that it is still fetching. - */ -- (void)completeBatchFetching:(BOOL)didComplete; - -/** - * Ask the context object if the batch fetching process was cancelled by the context owner. - * - * @discussion If an error occurs in the context owner, the batch fetching may become out of sync and need to be - * cancelled. For best practices, pass the return value of -batchWasCancelled to -completeBatchFetch:. - * - * @return A boolean reflecting if the context object owner had to cancel the batch process. - */ -- (BOOL)batchFetchingWasCancelled; - -/** - * Notify the context object that something has interrupted the batch fetching process. - * - * @discussion Call this method only when something has corrupted the batch fetching process. Calling this method should - * be left to the owner of the batch process unless there is a specific purpose. - */ -- (void)cancelBatchFetching; - -/** - * Notify the context object that fetching has started. - * - * @discussion Call this method only when you are beginning a fetch process. This should really only be called by the - * context object's owner. Calling this method should be paired with -completeBatchFetching:. - */ -- (void)beginBatchFetching; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASBatchContext.mm b/submodules/AsyncDisplayKit/Source/Details/ASBatchContext.mm deleted file mode 100644 index ee4ec173a5..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASBatchContext.mm +++ /dev/null @@ -1,64 +0,0 @@ -// -// ASBatchContext.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK -#import - -#import -#import - -typedef NS_ENUM(NSInteger, ASBatchContextState) { - ASBatchContextStateFetching, - ASBatchContextStateCancelled, - ASBatchContextStateCompleted -}; - -@implementation ASBatchContext { - atomic_int _state; -} - -- (instancetype)init -{ - if (self = [super init]) { - _state = ATOMIC_VAR_INIT(ASBatchContextStateCompleted); - } - return self; -} - -- (BOOL)isFetching -{ - return atomic_load(&_state) == ASBatchContextStateFetching; -} - -- (BOOL)batchFetchingWasCancelled -{ - return atomic_load(&_state) == ASBatchContextStateCancelled; -} - -- (void)beginBatchFetching -{ - atomic_store(&_state, ASBatchContextStateFetching); -} - -- (void)completeBatchFetching:(BOOL)didComplete -{ - if (didComplete) { - as_log_debug(ASCollectionLog(), "Completed batch fetch with context %@", self); - atomic_store(&_state, ASBatchContextStateCompleted); - } -} - -- (void)cancelBatchFetching -{ - atomic_store(&_state, ASBatchContextStateCancelled); -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASBatchFetchingDelegate.h b/submodules/AsyncDisplayKit/Source/Details/ASBatchFetchingDelegate.h deleted file mode 100644 index 3b35115026..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASBatchFetchingDelegate.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// ASBatchFetchingDelegate.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@protocol ASBatchFetchingDelegate - -/** - * @abstract Determine if batch fetching should begin based on the remaining time. - * If the delegate doesn't have enough information to confidently decide, it can take the given hint. - * - * @param remainingTime The amount of time left for user to reach the end of the scroll view's content. - * - * @param hint A hint for the delegate to fallback to. - */ -- (BOOL)shouldFetchBatchWithRemainingTime:(NSTimeInterval)remainingTime hint:(BOOL)hint; - -@end diff --git a/submodules/AsyncDisplayKit/Source/Details/ASCollectionElement.h b/submodules/AsyncDisplayKit/Source/Details/ASCollectionElement.h deleted file mode 100644 index ea8fc697c4..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASCollectionElement.h +++ /dev/null @@ -1,51 +0,0 @@ -// -// ASCollectionElement.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import - -@class ASDisplayNode; -@protocol ASRangeManagingNode; - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED -@interface ASCollectionElement : NSObject - -@property (nullable, nonatomic, copy, readonly) NSString *supplementaryElementKind; -@property (nonatomic) ASSizeRange constrainedSize; -@property (nonatomic, weak, readonly) id owningNode; -@property (nonatomic) ASPrimitiveTraitCollection traitCollection; -@property (nullable, nonatomic, readonly) id nodeModel; - -- (instancetype)initWithNodeModel:(nullable id)nodeModel - nodeBlock:(ASCellNodeBlock)nodeBlock - supplementaryElementKind:(nullable NSString *)supplementaryElementKind - constrainedSize:(ASSizeRange)constrainedSize - owningNode:(id)owningNode - traitCollection:(ASPrimitiveTraitCollection)traitCollection; - -/** - * @return The node, running the node block if necessary. The node block will be discarded - * after the first time it is run. - */ -@property (readonly) ASCellNode *node; - -/** - * @return The node, if the node block has been run already. - */ -@property (nullable, readonly) ASCellNode *nodeIfAllocated; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASCollectionElement.mm b/submodules/AsyncDisplayKit/Source/Details/ASCollectionElement.mm deleted file mode 100644 index 5b7b33862b..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASCollectionElement.mm +++ /dev/null @@ -1,91 +0,0 @@ -// -// ASCollectionElement.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK -#import -#import -#import - -@interface ASCollectionElement () - -/// Required node block used to allocate a cell node. Nil after the first execution. -@property (nonatomic) ASCellNodeBlock nodeBlock; - -@end - -@implementation ASCollectionElement { - std::mutex _lock; - ASCellNode *_node; -} - -- (instancetype)initWithNodeModel:(id)nodeModel - nodeBlock:(ASCellNodeBlock)nodeBlock - supplementaryElementKind:(NSString *)supplementaryElementKind - constrainedSize:(ASSizeRange)constrainedSize - owningNode:(id)owningNode - traitCollection:(ASPrimitiveTraitCollection)traitCollection -{ - NSAssert(nodeBlock != nil, @"Node block must not be nil"); - self = [super init]; - if (self) { - _nodeModel = nodeModel; - _nodeBlock = nodeBlock; - _supplementaryElementKind = [supplementaryElementKind copy]; - _constrainedSize = constrainedSize; - _owningNode = owningNode; - _traitCollection = traitCollection; - } - return self; -} - -- (ASCellNode *)node -{ - std::lock_guard l(_lock); - if (_nodeBlock != nil) { - ASCellNode *node = _nodeBlock(); - _nodeBlock = nil; - if (node == nil) { - ASDisplayNodeFailAssert(@"Node block returned nil node!"); - node = [[ASCellNode alloc] init]; - } - node.owningNode = _owningNode; - node.collectionElement = self; - ASTraitCollectionPropagateDown(node, _traitCollection); - node.nodeModel = _nodeModel; - _node = node; - } - return _node; -} - -- (ASCellNode *)nodeIfAllocated -{ - std::lock_guard l(_lock); - return _node; -} - -- (void)setTraitCollection:(ASPrimitiveTraitCollection)traitCollection -{ - ASCellNode *nodeIfNeedsPropagation; - - { - std::lock_guard l(_lock); - if (! ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(_traitCollection, traitCollection)) { - _traitCollection = traitCollection; - nodeIfNeedsPropagation = _node; - } - } - - if (nodeIfNeedsPropagation != nil) { - ASTraitCollectionPropagateDown(nodeIfNeedsPropagation, traitCollection); - } -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASCollectionFlowLayoutDelegate.h b/submodules/AsyncDisplayKit/Source/Details/ASCollectionFlowLayoutDelegate.h deleted file mode 100644 index ba6e5b48ed..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASCollectionFlowLayoutDelegate.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// ASCollectionFlowLayoutDelegate.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED - -/** - * A thread-safe, high performant layout delegate that arranges items into a flow layout. - * It uses a concurrent and multi-line ASStackLayoutSpec under the hood. Thus, per-child flex properties (i.e alignSelf, - * flexShrink, flexGrow, etc - see @ASStackLayoutElement) can be set directly on cell nodes to be used - * to calculate the final collection layout. - */ -@interface ASCollectionFlowLayoutDelegate : NSObject - -- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections NS_DESIGNATED_INITIALIZER; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASCollectionFlowLayoutDelegate.mm b/submodules/AsyncDisplayKit/Source/Details/ASCollectionFlowLayoutDelegate.mm deleted file mode 100644 index 37d311d24e..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASCollectionFlowLayoutDelegate.mm +++ /dev/null @@ -1,82 +0,0 @@ -// -// ASCollectionFlowLayoutDelegate.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import - -@implementation ASCollectionFlowLayoutDelegate { - ASScrollDirection _scrollableDirections; -} - -- (instancetype)init -{ - return [self initWithScrollableDirections:ASScrollDirectionVerticalDirections]; -} - -- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections -{ - self = [super init]; - if (self) { - _scrollableDirections = scrollableDirections; - } - return self; -} - -- (ASScrollDirection)scrollableDirections -{ - ASDisplayNodeAssertMainThread(); - return _scrollableDirections; -} - -- (id)additionalInfoForLayoutWithElements:(ASElementMap *)elements -{ - ASDisplayNodeAssertMainThread(); - return nil; -} - -+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context -{ - ASElementMap *elements = context.elements; - NSArray *children = ASArrayByFlatMapping(elements.itemElements, ASCollectionElement *element, element.node); - if (children.count == 0) { - return [[ASCollectionLayoutState alloc] initWithContext:context]; - } - - ASStackLayoutSpec *stackSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal - spacing:0 - justifyContent:ASStackLayoutJustifyContentStart - alignItems:ASStackLayoutAlignItemsStart - flexWrap:ASStackLayoutFlexWrapWrap - alignContent:ASStackLayoutAlignContentStart - children:children]; - stackSpec.concurrent = YES; - - ASSizeRange sizeRange = ASSizeRangeForCollectionLayoutThatFitsViewportSize(context.viewportSize, context.scrollableDirections); - ASLayout *layout = [stackSpec layoutThatFits:sizeRange]; - - return [[ASCollectionLayoutState alloc] initWithContext:context layout:layout getElementBlock:^ASCollectionElement * _Nullable(ASLayout * _Nonnull sublayout) { - ASCellNode *node = ASDynamicCast(sublayout.layoutElement, ASCellNode); - return node ? node.collectionElement : nil; - }]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASCollectionGalleryLayoutDelegate.h b/submodules/AsyncDisplayKit/Source/Details/ASCollectionGalleryLayoutDelegate.h deleted file mode 100644 index c6d1df2cb7..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASCollectionGalleryLayoutDelegate.h +++ /dev/null @@ -1,110 +0,0 @@ -// -// ASCollectionGalleryLayoutDelegate.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import - -@class ASElementMap; -@class ASCollectionGalleryLayoutDelegate; - -NS_ASSUME_NONNULL_BEGIN - -@protocol ASCollectionGalleryLayoutPropertiesProviding - -/** - * Returns the fixed size of each and every element. - * - * @discussion This method will only be called on main thread. - * - * @param delegate The calling object. - * - * @param elements All elements to be sized. - * - * @return The elements' size - */ -- (CGSize)galleryLayoutDelegate:(ASCollectionGalleryLayoutDelegate *)delegate sizeForElements:(ASElementMap *)elements; - -@optional - -/** - * Returns the minumum spacing to use between lines of items. - * - * @discussion This method will only be called on main thread. - * - * @discussion For a vertically scrolling layout, this value represents the minimum spacing between rows. - * For a horizontally scrolling one, it represents the minimum spacing between columns. - * It is not applied between the first line and the header, or between the last line and the footer. - * This is the same behavior as UICollectionViewFlowLayout's minimumLineSpacing. - * - * @param delegate The calling object. - * - * @param elements All elements in the layout. - * - * @return The interitem spacing - */ -- (CGFloat)galleryLayoutDelegate:(ASCollectionGalleryLayoutDelegate *)delegate minimumLineSpacingForElements:(ASElementMap *)elements; - -/** - * Returns the minumum spacing to use between items in the same row or column, depending on the scroll directions. - * - * @discussion This method will only be called on main thread. - * - * @discussion For a vertically scrolling layout, this value represents the minimum spacing between items in the same row. - * For a horizontally scrolling one, it represents the minimum spacing between items in the same column. - * It is considered while fitting items into lines, but the actual final spacing between some items might be larger. - * This is the same behavior as UICollectionViewFlowLayout's minimumInteritemSpacing. - * - * @param delegate The calling object. - * - * @param elements All elements in the layout. - * - * @return The interitem spacing - */ -- (CGFloat)galleryLayoutDelegate:(ASCollectionGalleryLayoutDelegate *)delegate minimumInteritemSpacingForElements:(ASElementMap *)elements; - -/** - * Returns the margins of each section. - * - * @discussion This method will only be called on main thread. - * - * @param delegate The calling object. - * - * @param elements All elements in the layout. - * - * @return The margins used to layout content in a section - */ -- (UIEdgeInsets)galleryLayoutDelegate:(ASCollectionGalleryLayoutDelegate *)delegate sectionInsetForElements:(ASElementMap *)elements; - -@end - -/** - * A thread-safe layout delegate that arranges items with the same size into a flow layout. - * - * @note Supplemenraty elements are not supported. - */ -AS_SUBCLASSING_RESTRICTED -@interface ASCollectionGalleryLayoutDelegate : NSObject - -@property (nonatomic, weak) id propertiesProvider; - -/** - * Designated initializer. - * - * @param scrollableDirections The scrollable directions of this layout. Must be either vertical or horizontal directions. - */ -- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections NS_DESIGNATED_INITIALIZER; - -- (instancetype)init __unavailable; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASCollectionGalleryLayoutDelegate.m b/submodules/AsyncDisplayKit/Source/Details/ASCollectionGalleryLayoutDelegate.m deleted file mode 100644 index 99e65f8343..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASCollectionGalleryLayoutDelegate.m +++ /dev/null @@ -1,97 +0,0 @@ -// -// ASCollectionGalleryLayoutDelegate.m -// Texture -// -// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#pragma mark - ASCollectionGalleryLayoutDelegate - -@implementation ASCollectionGalleryLayoutDelegate { - ASScrollDirection _scrollableDirections; - CGSize _itemSize; -} - -- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections -{ - self = [super init]; - if (self) { - _scrollableDirections = scrollableDirections; - } - return self; -} - -- (ASScrollDirection)scrollableDirections -{ - ASDisplayNodeAssertMainThread(); - return _scrollableDirections; -} - -- (id)additionalInfoForLayoutWithElements:(ASElementMap *)elements -{ - ASDisplayNodeAssertMainThread(); - if (_sizeProvider == nil) { - return nil; - } - - return [NSValue valueWithCGSize:[_sizeProvider sizeForElements:elements]]; -} - -+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context -{ - ASElementMap *elements = context.elements; - CGSize pageSize = context.viewportSize; - ASScrollDirection scrollableDirections = context.scrollableDirections; - - CGSize itemSize = context.additionalInfo ? ((NSValue *)context.additionalInfo).CGSizeValue : CGSizeZero; - if (CGSizeEqualToSize(CGSizeZero, itemSize)) { - return [[ASCollectionLayoutState alloc] initWithContext:context]; - } - - NSMutableArray<_ASGalleryLayoutItem *> *children = ASArrayByFlatMapping(elements.itemElements, - ASCollectionElement *element, - [[_ASGalleryLayoutItem alloc] initWithItemSize:itemSize collectionElement:element]); - if (children.count == 0) { - return [[ASCollectionLayoutState alloc] initWithContext:context]; - } - - // Use a stack spec to calculate layout content size and frames of all elements without actually measuring each element - ASStackLayoutSpec *stackSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal - spacing:0 - justifyContent:ASStackLayoutJustifyContentStart - alignItems:ASStackLayoutAlignItemsStart - flexWrap:ASStackLayoutFlexWrapWrap - alignContent:ASStackLayoutAlignContentStart - children:children]; - stackSpec.concurrent = YES; - ASLayout *layout = [stackSpec layoutThatFits:ASSizeRangeForCollectionLayoutThatFitsViewportSize(pageSize, scrollableDirections)]; - - return [[ASCollectionLayoutState alloc] initWithContext:context layout:layout getElementBlock:^ASCollectionElement *(ASLayout *sublayout) { - return ((_ASGalleryLayoutItem *)sublayout.layoutElement).collectionElement; - }]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASCollectionGalleryLayoutDelegate.mm b/submodules/AsyncDisplayKit/Source/Details/ASCollectionGalleryLayoutDelegate.mm deleted file mode 100644 index 94d1f0de36..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASCollectionGalleryLayoutDelegate.mm +++ /dev/null @@ -1,141 +0,0 @@ -// -// ASCollectionGalleryLayoutDelegate.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#pragma mark - ASCollectionGalleryLayoutDelegate - -@implementation ASCollectionGalleryLayoutDelegate { - ASScrollDirection _scrollableDirections; - - struct { - unsigned int minimumLineSpacingForElements:1; - unsigned int minimumInteritemSpacingForElements:1; - unsigned int sectionInsetForElements:1; - } _propertiesProviderFlags; -} - -- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections -{ - self = [super init]; - if (self) { - // Scrollable directions must be either vertical or horizontal, but not both - ASDisplayNodeAssertTrue(ASScrollDirectionContainsVerticalDirection(scrollableDirections) - || ASScrollDirectionContainsHorizontalDirection(scrollableDirections)); - ASDisplayNodeAssertFalse(ASScrollDirectionContainsVerticalDirection(scrollableDirections) - && ASScrollDirectionContainsHorizontalDirection(scrollableDirections)); - _scrollableDirections = scrollableDirections; - } - return self; -} - -- (ASScrollDirection)scrollableDirections -{ - ASDisplayNodeAssertMainThread(); - return _scrollableDirections; -} - -- (void)setPropertiesProvider:(id)propertiesProvider -{ - ASDisplayNodeAssertMainThread(); - if (propertiesProvider == nil) { - _propertiesProvider = nil; - _propertiesProviderFlags = {}; - } else { - _propertiesProvider = propertiesProvider; - _propertiesProviderFlags.minimumLineSpacingForElements = [_propertiesProvider respondsToSelector:@selector(galleryLayoutDelegate:minimumLineSpacingForElements:)]; - _propertiesProviderFlags.minimumInteritemSpacingForElements = [_propertiesProvider respondsToSelector:@selector(galleryLayoutDelegate:minimumInteritemSpacingForElements:)]; - _propertiesProviderFlags.sectionInsetForElements = [_propertiesProvider respondsToSelector:@selector(galleryLayoutDelegate:sectionInsetForElements:)]; - } -} - -- (id)additionalInfoForLayoutWithElements:(ASElementMap *)elements -{ - ASDisplayNodeAssertMainThread(); - id propertiesProvider = _propertiesProvider; - if (propertiesProvider == nil) { - return nil; - } - - CGSize itemSize = [propertiesProvider galleryLayoutDelegate:self sizeForElements:elements]; - UIEdgeInsets sectionInset = _propertiesProviderFlags.sectionInsetForElements ? [propertiesProvider galleryLayoutDelegate:self sectionInsetForElements:elements] : UIEdgeInsetsZero; - CGFloat lineSpacing = _propertiesProviderFlags.minimumLineSpacingForElements ? [propertiesProvider galleryLayoutDelegate:self minimumLineSpacingForElements:elements] : 0.0; - CGFloat interitemSpacing = _propertiesProviderFlags.minimumInteritemSpacingForElements ? [propertiesProvider galleryLayoutDelegate:self minimumInteritemSpacingForElements:elements] : 0.0; - return [[_ASCollectionGalleryLayoutInfo alloc] initWithItemSize:itemSize - minimumLineSpacing:lineSpacing - minimumInteritemSpacing:interitemSpacing - sectionInset:sectionInset]; -} - -+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context -{ - ASElementMap *elements = context.elements; - CGSize pageSize = context.viewportSize; - ASScrollDirection scrollableDirections = context.scrollableDirections; - - _ASCollectionGalleryLayoutInfo *info = ASDynamicCast(context.additionalInfo, _ASCollectionGalleryLayoutInfo); - CGSize itemSize = info.itemSize; - if (info == nil || CGSizeEqualToSize(CGSizeZero, itemSize)) { - return [[ASCollectionLayoutState alloc] initWithContext:context]; - } - - NSArray<_ASGalleryLayoutItem *> *children = ASArrayByFlatMapping(elements.itemElements, - ASCollectionElement *element, - [[_ASGalleryLayoutItem alloc] initWithItemSize:itemSize collectionElement:element]); - if (children.count == 0) { - return [[ASCollectionLayoutState alloc] initWithContext:context]; - } - - // Use a stack spec to calculate layout content size and frames of all elements without actually measuring each element - ASStackLayoutDirection stackDirection = ASScrollDirectionContainsVerticalDirection(scrollableDirections) - ? ASStackLayoutDirectionHorizontal - : ASStackLayoutDirectionVertical; - ASStackLayoutSpec *stackSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:stackDirection - spacing:info.minimumInteritemSpacing - justifyContent:ASStackLayoutJustifyContentStart - alignItems:ASStackLayoutAlignItemsStart - flexWrap:ASStackLayoutFlexWrapWrap - alignContent:ASStackLayoutAlignContentStart - lineSpacing:info.minimumLineSpacing - children:children]; - stackSpec.concurrent = YES; - - ASLayoutSpec *finalSpec = stackSpec; - UIEdgeInsets sectionInset = info.sectionInset; - if (UIEdgeInsetsEqualToEdgeInsets(sectionInset, UIEdgeInsetsZero) == NO) { - finalSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:sectionInset child:stackSpec]; - } - - ASLayout *layout = [finalSpec layoutThatFits:ASSizeRangeForCollectionLayoutThatFitsViewportSize(pageSize, scrollableDirections)]; - - return [[ASCollectionLayoutState alloc] initWithContext:context layout:layout getElementBlock:^ASCollectionElement * _Nullable(ASLayout * _Nonnull sublayout) { - _ASGalleryLayoutItem *item = ASDynamicCast(sublayout.layoutElement, _ASGalleryLayoutItem); - return item ? item.collectionElement : nil; - }]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASCollectionInternal.h b/submodules/AsyncDisplayKit/Source/Details/ASCollectionInternal.h deleted file mode 100644 index 925b19f970..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASCollectionInternal.h +++ /dev/null @@ -1,68 +0,0 @@ -// -// ASCollectionInternal.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -NS_ASSUME_NONNULL_BEGIN - -@protocol ASCollectionViewLayoutFacilitatorProtocol; -@class ASCollectionNode; -@class ASDataController; -@class ASRangeController; - -@interface ASCollectionView () -- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(nullable id)layoutFacilitator owningNode:(nullable ASCollectionNode *)owningNode eventLog:(nullable ASEventLog *)eventLog; - -@property (nonatomic, weak) ASCollectionNode *collectionNode; -@property (nonatomic, readonly) ASDataController *dataController; -@property (nonatomic, readonly) ASRangeController *rangeController; - -/** - * The change set that we're currently building, if any. - */ -@property (nonatomic, nullable, readonly) _ASHierarchyChangeSet *changeSet; - -/** - * @see ASCollectionNode+Beta.h for full documentation. - */ -@property (nonatomic) ASCellLayoutMode cellLayoutMode; - -/** - * Attempt to get the view-layer index path for the item with the given index path. - * - * @param indexPath The index path of the item. - * @param wait If the item hasn't reached the view yet, this attempts to wait for updates to commit. - */ -- (nullable NSIndexPath *)convertIndexPathFromCollectionNode:(NSIndexPath *)indexPath waitingIfNeeded:(BOOL)wait; - -/** - * Attempt to get the node index path given the view-layer index path. - * - * @param indexPath The index path of the row. - */ -- (nullable NSIndexPath *)convertIndexPathToCollectionNode:(NSIndexPath *)indexPath; - -/** - * Attempt to get the node index paths given the view-layer index paths. - * - * @param indexPaths An array of index paths in the view space - */ -- (nullable NSArray *)convertIndexPathsToCollectionNode:(nullable NSArray *)indexPaths; - -- (void)beginUpdates; - -- (void)endUpdatesAnimated:(BOOL)animated completion:(nullable void (^)(BOOL))completion; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASCollectionLayoutContext.h b/submodules/AsyncDisplayKit/Source/Details/ASCollectionLayoutContext.h deleted file mode 100644 index f5e875f561..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASCollectionLayoutContext.h +++ /dev/null @@ -1,35 +0,0 @@ -// -// ASCollectionLayoutContext.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import -#import - -@class ASElementMap; - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED -@interface ASCollectionLayoutContext : NSObject - -@property (nonatomic, readonly) CGSize viewportSize; -@property (nonatomic, readonly) CGPoint initialContentOffset; -@property (nonatomic, readonly) ASScrollDirection scrollableDirections; -@property (nonatomic, weak, readonly) ASElementMap *elements; -@property (nullable, nonatomic, readonly) id additionalInfo; - -- (instancetype)init NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASCollectionLayoutContext.mm b/submodules/AsyncDisplayKit/Source/Details/ASCollectionLayoutContext.mm deleted file mode 100644 index 5f82fd76fa..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASCollectionLayoutContext.mm +++ /dev/null @@ -1,107 +0,0 @@ -// -// ASCollectionLayoutContext.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import - -#import -#import -#import -#import -#import -#import - -@implementation ASCollectionLayoutContext { - Class _layoutDelegateClass; - __weak ASCollectionLayoutCache *_layoutCache; -} - -- (instancetype)initWithViewportSize:(CGSize)viewportSize - initialContentOffset:(CGPoint)initialContentOffset - scrollableDirections:(ASScrollDirection)scrollableDirections - elements:(ASElementMap *)elements - layoutDelegateClass:(Class)layoutDelegateClass - layoutCache:(ASCollectionLayoutCache *)layoutCache - additionalInfo:(id)additionalInfo -{ - self = [super init]; - if (self) { - _viewportSize = viewportSize; - _initialContentOffset = initialContentOffset; - _scrollableDirections = scrollableDirections; - _elements = elements; - _layoutDelegateClass = layoutDelegateClass; - _layoutCache = layoutCache; - _additionalInfo = additionalInfo; - } - return self; -} - -- (Class)layoutDelegateClass -{ - return _layoutDelegateClass; -} - -- (ASCollectionLayoutCache *)layoutCache -{ - return _layoutCache; -} - -// NOTE: Some properties, like initialContentOffset and layoutCache are ignored in -isEqualToContext: and -hash. -// That is because contexts can be equal regardless of the content offsets or layout caches. -- (BOOL)isEqualToContext:(ASCollectionLayoutContext *)context -{ - if (context == nil) { - return NO; - } - - // NOTE: ASObjectIsEqual returns YES when both objects are nil. - // So don't use ASObjectIsEqual on _elements. - // It is a weak property and 2 layouts generated from different sets of elements - // should never be considered the same even if they are nil now. - return CGSizeEqualToSize(_viewportSize, context.viewportSize) - && _scrollableDirections == context.scrollableDirections - && [_elements isEqual:context.elements] - && _layoutDelegateClass == context.layoutDelegateClass - && ASObjectIsEqual(_additionalInfo, context.additionalInfo); -} - -- (BOOL)isEqual:(id)other -{ - if (self == other) { - return YES; - } - if (! [other isKindOfClass:[ASCollectionLayoutContext class]]) { - return NO; - } - return [self isEqualToContext:other]; -} - -- (NSUInteger)hash -{ - struct { - CGSize viewportSize; - ASScrollDirection scrollableDirections; - NSUInteger elementsHash; - NSUInteger layoutDelegateClassHash; - NSUInteger additionalInfoHash; - } data = { - _viewportSize, - _scrollableDirections, - _elements.hash, - _layoutDelegateClass.hash, - [_additionalInfo hash] - }; - return ASHashBytes(&data, sizeof(data)); -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASCollectionLayoutDelegate.h b/submodules/AsyncDisplayKit/Source/Details/ASCollectionLayoutDelegate.h deleted file mode 100644 index d92a8c0ab7..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASCollectionLayoutDelegate.h +++ /dev/null @@ -1,63 +0,0 @@ -// -// ASCollectionLayoutDelegate.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import -#import - -@class ASElementMap, ASCollectionLayoutContext, ASCollectionLayoutState; - -NS_ASSUME_NONNULL_BEGIN - -@protocol ASCollectionLayoutDelegate - -/** - * @abstract Returns the scrollable directions of the coming layout (@see @c -calculateLayoutWithContext:). - * It will be available in the context parameter in +calculateLayoutWithContext: - * - * @return The scrollable directions. - * - * @discusstion This method will be called on main thread. - */ -- (ASScrollDirection)scrollableDirections; - -/** - * @abstract Returns any additional information needed for a coming layout pass (@see @c -calculateLayoutWithContext:) with the given elements. - * - * @param elements The elements to be laid out later. - * - * @discussion The returned object must support equality and hashing (i.e `-isEqual:` and `-hash` must be properly implemented). - * It should contain all the information needed for the layout pass to perform. It will be available in the context parameter in +calculateLayoutWithContext: - * - * This method will be called on main thread. - */ -- (nullable id)additionalInfoForLayoutWithElements:(ASElementMap *)elements; - -/** - * @abstract Prepares and returns a new layout for given context. - * - * @param context A context that contains all elements to be laid out and any additional information needed. - * - * @return The new layout calculated for the given context. - * - * @discussion This method is called ahead of time, i.e before the underlying collection/table view is aware of the provided elements. - * As a result, clients must solely rely on the given context and should not reach out to other objects for information not available in the context. - * - * This method can be called on background theads. It must be thread-safe and should not change any internal state of this delegate. - * It must block the calling thread but can dispatch to other theads to reduce total blocking time. - */ -+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASCollectionLayoutState.h b/submodules/AsyncDisplayKit/Source/Details/ASCollectionLayoutState.h deleted file mode 100644 index 461f4b1296..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASCollectionLayoutState.h +++ /dev/null @@ -1,115 +0,0 @@ -// -// ASCollectionLayoutState.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import -#import - -@class ASCollectionLayoutContext, ASLayout, ASCollectionElement; - -NS_ASSUME_NONNULL_BEGIN - -typedef ASCollectionElement * _Nullable (^ASCollectionLayoutStateGetElementBlock)(ASLayout *); - -@interface NSMapTable (ASCollectionLayoutConvenience) - -+ (NSMapTable *)elementToLayoutAttributesTable; - -@end - -AS_SUBCLASSING_RESTRICTED - -/// An immutable state of the collection layout -@interface ASCollectionLayoutState : NSObject - -/// The context used to calculate this object -@property (readonly) ASCollectionLayoutContext *context; - -/// The final content size of the collection's layout -@property (readonly) CGSize contentSize; - -- (instancetype)init NS_UNAVAILABLE; - -/** - * Designated initializer. - * - * @param context The context used to calculate this object - * - * @param contentSize The content size of the collection's layout - * - * @param table A map between elements to their layout attributes. It must contain all elements. - * It should have NSMapTableObjectPointerPersonality and NSMapTableWeakMemory as key options. - */ -- (instancetype)initWithContext:(ASCollectionLayoutContext *)context - contentSize:(CGSize)contentSize - elementToLayoutAttributesTable:(NSMapTable *)table NS_DESIGNATED_INITIALIZER; - -/** - * Convenience initializer. Returns an object with zero content size and an empty table. - * - * @param context The context used to calculate this object - */ -- (instancetype)initWithContext:(ASCollectionLayoutContext *)context; - -/** - * Convenience initializer. - * - * @param context The context used to calculate this object - * - * @param layout The layout describes size and position of all elements. - * - * @param getElementBlock A block that can retrieve the collection element from a sublayout of the root layout. - */ -- (instancetype)initWithContext:(ASCollectionLayoutContext *)context - layout:(ASLayout *)layout - getElementBlock:(ASCollectionLayoutStateGetElementBlock)getElementBlock; - -/** - * Returns all layout attributes present in this object. - */ -- (NSArray *)allLayoutAttributes; - -/** - * Returns layout attributes of elements in the specified rect. - * - * @param rect The rect containing the target elements. - */ -- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect; - -/** - * Returns layout attributes of the element at the specified index path. - * - * @param indexPath The index path of the item. - */ -- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath; - -/** - * Returns layout attributes of the specified supplementary element. - * - * @param kind A string that identifies the type of the supplementary element. - * - * @param indexPath The index path of the element. - */ -- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryElementOfKind:(NSString *)kind - atIndexPath:(NSIndexPath *)indexPath; - -/** - * Returns layout attributes of the specified element. - * - * @element The element. - */ -- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForElement:(ASCollectionElement *)element; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASCollectionLayoutState.mm b/submodules/AsyncDisplayKit/Source/Details/ASCollectionLayoutState.mm deleted file mode 100644 index 2c09202c1b..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASCollectionLayoutState.mm +++ /dev/null @@ -1,259 +0,0 @@ -// -// ASCollectionLayoutState.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#import - -@implementation NSMapTable (ASCollectionLayoutConvenience) - -+ (NSMapTable *)elementToLayoutAttributesTable -{ - return [NSMapTable mapTableWithKeyOptions:(NSMapTableWeakMemory | NSMapTableObjectPointerPersonality) valueOptions:NSMapTableStrongMemory]; -} - -@end - -@implementation ASCollectionLayoutState { - AS::Mutex __instanceLock__; - CGSize _contentSize; - ASCollectionLayoutContext *_context; - NSMapTable *_elementToLayoutAttributesTable; - ASPageToLayoutAttributesTable *_pageToLayoutAttributesTable; - ASPageToLayoutAttributesTable *_unmeasuredPageToLayoutAttributesTable; -} - -- (instancetype)initWithContext:(ASCollectionLayoutContext *)context -{ - return [self initWithContext:context - contentSize:CGSizeZero -elementToLayoutAttributesTable:[NSMapTable elementToLayoutAttributesTable]]; -} - -- (instancetype)initWithContext:(ASCollectionLayoutContext *)context - layout:(ASLayout *)layout - getElementBlock:(ASCollectionLayoutStateGetElementBlock)getElementBlock -{ - ASElementMap *elements = context.elements; - NSMapTable *table = [NSMapTable elementToLayoutAttributesTable]; - - // Traverse the given layout tree in breadth first fashion. Generate layout attributes for all included elements along the way. - struct Context { - ASLayout *layout; - CGPoint absolutePosition; - }; - - std::queue queue; - queue.push({layout, CGPointZero}); - - while (!queue.empty()) { - Context context = queue.front(); - queue.pop(); - - ASLayout *layout = context.layout; - const CGPoint absolutePosition = context.absolutePosition; - - ASCollectionElement *element = getElementBlock(layout); - if (element != nil) { - NSIndexPath *indexPath = [elements indexPathForElement:element]; - NSString *supplementaryElementKind = element.supplementaryElementKind; - - UICollectionViewLayoutAttributes *attrs; - if (supplementaryElementKind == nil) { - attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; - } else { - attrs = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:supplementaryElementKind withIndexPath:indexPath]; - } - - CGRect frame = layout.frame; - frame.origin = absolutePosition; - attrs.frame = frame; - [table setObject:attrs forKey:element]; - } - - // Add all sublayouts to process in next step - for (ASLayout *sublayout in layout.sublayouts) { - queue.push({sublayout, absolutePosition + sublayout.position}); - } - } - - return [self initWithContext:context contentSize:layout.size elementToLayoutAttributesTable:table]; -} - -- (instancetype)initWithContext:(ASCollectionLayoutContext *)context - contentSize:(CGSize)contentSize - elementToLayoutAttributesTable:(NSMapTable *)table -{ - self = [super init]; - if (self) { - _context = context; - _contentSize = contentSize; - _elementToLayoutAttributesTable = [table copy]; // Copy the given table to make sure clients can't mutate it after this point. - CGSize pageSize = context.viewportSize; - _pageToLayoutAttributesTable = [ASPageTable pageTableWithLayoutAttributes:table.objectEnumerator contentSize:contentSize pageSize:pageSize]; - _unmeasuredPageToLayoutAttributesTable = [ASCollectionLayoutState _unmeasuredLayoutAttributesTableFromTable:table contentSize:contentSize pageSize:pageSize]; - } - return self; -} - -- (ASCollectionLayoutContext *)context -{ - return _context; -} - -- (CGSize)contentSize -{ - return _contentSize; -} - -- (NSArray *)allLayoutAttributes -{ - return [_elementToLayoutAttributesTable.objectEnumerator allObjects]; -} - -- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath -{ - ASCollectionElement *element = [_context.elements elementForItemAtIndexPath:indexPath]; - return [_elementToLayoutAttributesTable objectForKey:element]; -} - -- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryElementOfKind:(NSString *)elementKind - atIndexPath:(NSIndexPath *)indexPath -{ - ASCollectionElement *element = [_context.elements supplementaryElementOfKind:elementKind atIndexPath:indexPath]; - return [_elementToLayoutAttributesTable objectForKey:element]; -} - -- (UICollectionViewLayoutAttributes *)layoutAttributesForElement:(ASCollectionElement *)element -{ - return [_elementToLayoutAttributesTable objectForKey:element]; -} - -- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect -{ - CGSize pageSize = _context.viewportSize; - NSPointerArray *pages = ASPageCoordinatesForPagesThatIntersectRect(rect, _contentSize, pageSize); - if (pages.count == 0) { - return @[]; - } - - // Use a set here because some items may span multiple pages - const auto result = [[NSMutableSet alloc] init]; - for (id pagePtr in pages) { - ASPageCoordinate page = (ASPageCoordinate)pagePtr; - NSArray *allAttrs = [_pageToLayoutAttributesTable objectForPage:page]; - if (allAttrs.count > 0) { - CGRect pageRect = ASPageCoordinateGetPageRect(page, pageSize); - - if (CGRectContainsRect(rect, pageRect)) { - [result addObjectsFromArray:allAttrs]; - } else { - for (UICollectionViewLayoutAttributes *attrs in allAttrs) { - if (CGRectIntersectsRect(rect, attrs.frame)) { - [result addObject:attrs]; - } - } - } - } - } - - return [result allObjects]; -} - -- (ASPageToLayoutAttributesTable *)getAndRemoveUnmeasuredLayoutAttributesPageTableInRect:(CGRect)rect -{ - CGSize pageSize = _context.viewportSize; - CGSize contentSize = _contentSize; - - AS::MutexLocker l(__instanceLock__); - if (_unmeasuredPageToLayoutAttributesTable.count == 0 || CGRectIsNull(rect) || CGRectIsEmpty(rect) || CGSizeEqualToSize(CGSizeZero, contentSize) || CGSizeEqualToSize(CGSizeZero, pageSize)) { - return nil; - } - - // Step 1: Determine all the pages that intersect the specified rect - NSPointerArray *pagesInRect = ASPageCoordinatesForPagesThatIntersectRect(rect, contentSize, pageSize); - if (pagesInRect.count == 0) { - return nil; - } - - // Step 2: Filter out attributes in these pages that intersect the specified rect. - ASPageToLayoutAttributesTable *result = nil; - for (id pagePtr in pagesInRect) { - ASPageCoordinate page = (ASPageCoordinate)pagePtr; - NSMutableArray *attrsInPage = [_unmeasuredPageToLayoutAttributesTable objectForPage:page]; - if (attrsInPage.count == 0) { - continue; - } - - NSMutableArray *intersectingAttrsInPage = nil; - CGRect pageRect = ASPageCoordinateGetPageRect(page, pageSize); - if (CGRectContainsRect(rect, pageRect)) { - // This page fits well within the specified rect. Simply return all of its attributes. - intersectingAttrsInPage = attrsInPage; - } else { - // The page intersects the specified rect. Some attributes in this page are returned, some are not. - for (UICollectionViewLayoutAttributes *attrs in attrsInPage) { - if (CGRectIntersectsRect(rect, attrs.frame)) { - if (intersectingAttrsInPage == nil) { - intersectingAttrsInPage = [[NSMutableArray alloc] init]; - } - [intersectingAttrsInPage addObject:attrs]; - } - } - } - - if (intersectingAttrsInPage.count > 0) { - if (attrsInPage.count == intersectingAttrsInPage.count) { - [_unmeasuredPageToLayoutAttributesTable removeObjectForPage:page]; - } else { - [attrsInPage removeObjectsInArray:intersectingAttrsInPage]; - } - if (result == nil) { - result = [ASPageTable pageTableForStrongObjectPointers]; - } - [result setObject:intersectingAttrsInPage forPage:page]; - } - } - - return result; -} - -#pragma mark - Private methods - -+ (ASPageToLayoutAttributesTable *)_unmeasuredLayoutAttributesTableFromTable:(NSMapTable *)table - contentSize:(CGSize)contentSize - pageSize:(CGSize)pageSize -{ - NSMutableArray *unmeasuredAttrs = [[NSMutableArray alloc] init]; - for (ASCollectionElement *element in table) { - UICollectionViewLayoutAttributes *attrs = [table objectForKey:element]; - if (element.nodeIfAllocated == nil || CGSizeEqualToSize(element.nodeIfAllocated.calculatedSize, attrs.frame.size) == NO) { - [unmeasuredAttrs addObject:attrs]; - } - } - - return [ASPageTable pageTableWithLayoutAttributes:unmeasuredAttrs contentSize:contentSize pageSize:pageSize]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASCollectionViewLayoutController.h b/submodules/AsyncDisplayKit/Source/Details/ASCollectionViewLayoutController.h deleted file mode 100644 index 9441486b54..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASCollectionViewLayoutController.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// ASCollectionViewLayoutController.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -NS_ASSUME_NONNULL_BEGIN - -@class ASCollectionView; - -AS_SUBCLASSING_RESTRICTED -@interface ASCollectionViewLayoutController : ASAbstractLayoutController - -- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASCollectionViewLayoutController.mm b/submodules/AsyncDisplayKit/Source/Details/ASCollectionViewLayoutController.mm deleted file mode 100644 index 1d7f1ca013..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASCollectionViewLayoutController.mm +++ /dev/null @@ -1,130 +0,0 @@ -// -// ASCollectionViewLayoutController.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK -#import - -#import -#import -#import -#import -#import - -struct ASRangeGeometry { - CGRect rangeBounds; - CGRect updateBounds; -}; -typedef struct ASRangeGeometry ASRangeGeometry; - -#pragma mark - -#pragma mark ASCollectionViewLayoutController - -@interface ASCollectionViewLayoutController () -{ - @package - ASCollectionView * __weak _collectionView; - UICollectionViewLayout * __strong _collectionViewLayout; -} -@end - -@implementation ASCollectionViewLayoutController - -- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView -{ - if (!(self = [super init])) { - return nil; - } - - _collectionView = collectionView; - _collectionViewLayout = [collectionView collectionViewLayout]; - return self; -} - -- (NSHashTable *)elementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType map:(ASElementMap *)map -{ - ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeMode:rangeMode rangeType:rangeType]; - CGRect rangeBounds = [self rangeBoundsWithScrollDirection:scrollDirection rangeTuningParameters:tuningParameters]; - return [self elementsWithinRangeBounds:rangeBounds map:map]; -} - -- (void)allElementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode displaySet:(NSHashTable *__autoreleasing _Nullable *)displaySet preloadSet:(NSHashTable *__autoreleasing _Nullable *)preloadSet map:(ASElementMap *)map -{ - if (displaySet == NULL || preloadSet == NULL) { - return; - } - - ASRangeTuningParameters displayParams = [self tuningParametersForRangeMode:rangeMode rangeType:ASLayoutRangeTypeDisplay]; - ASRangeTuningParameters preloadParams = [self tuningParametersForRangeMode:rangeMode rangeType:ASLayoutRangeTypePreload]; - CGRect displayBounds = [self rangeBoundsWithScrollDirection:scrollDirection rangeTuningParameters:displayParams]; - CGRect preloadBounds = [self rangeBoundsWithScrollDirection:scrollDirection rangeTuningParameters:preloadParams]; - - CGRect unionBounds = CGRectUnion(displayBounds, preloadBounds); - NSArray *layoutAttributes = [_collectionViewLayout layoutAttributesForElementsInRect:unionBounds]; - NSInteger count = layoutAttributes.count; - - __auto_type display = [[NSHashTable alloc] initWithOptions:NSHashTableObjectPointerPersonality capacity:count]; - __auto_type preload = [[NSHashTable alloc] initWithOptions:NSHashTableObjectPointerPersonality capacity:count]; - - for (UICollectionViewLayoutAttributes *la in layoutAttributes) { - // Manually filter out elements that don't intersect the range bounds. - // See comment in elementsForItemsWithinRangeBounds: - // This is re-implemented here so that the iteration over layoutAttributes can be done once to check both ranges. - CGRect frame = la.frame; - BOOL intersectsDisplay = CGRectIntersectsRect(displayBounds, frame); - BOOL intersectsPreload = CGRectIntersectsRect(preloadBounds, frame); - if (intersectsDisplay == NO && intersectsPreload == NO && CATransform3DIsIdentity(la.transform3D) == YES) { - // Questionable why the element would be included here, but it doesn't belong. - continue; - } - - // Avoid excessive retains and releases, as well as property calls. We know the element is kept alive by map. - __unsafe_unretained ASCollectionElement *e = [map elementForLayoutAttributes:la]; - if (e != nil && intersectsDisplay) { - [display addObject:e]; - } - if (e != nil && intersectsPreload) { - [preload addObject:e]; - } - } - - *displaySet = display; - *preloadSet = preload; - return; -} - -- (NSHashTable *)elementsWithinRangeBounds:(CGRect)rangeBounds map:(ASElementMap *)map -{ - NSArray *layoutAttributes = [_collectionViewLayout layoutAttributesForElementsInRect:rangeBounds]; - NSHashTable *elementSet = [[NSHashTable alloc] initWithOptions:NSHashTableObjectPointerPersonality capacity:layoutAttributes.count]; - - for (UICollectionViewLayoutAttributes *la in layoutAttributes) { - // Manually filter out elements that don't intersect the range bounds. - // If a layout returns elements outside the requested rect this can be a huge problem. - // For instance in a paging flow, you may only want to preload 3 pages (one center, one on each side) - // but if flow layout includes the 4th page (which it does! as of iOS 9&10), you will preload a 4th - // page as well. - if (CATransform3DIsIdentity(la.transform3D) && CGRectIntersectsRect(la.frame, rangeBounds) == NO) { - continue; - } - [elementSet addObject:[map elementForLayoutAttributes:la]]; - } - - return elementSet; -} - -- (CGRect)rangeBoundsWithScrollDirection:(ASScrollDirection)scrollDirection - rangeTuningParameters:(ASRangeTuningParameters)tuningParameters -{ - CGRect rect = _collectionView.bounds; - - return CGRectExpandToRangeWithScrollableDirections(rect, tuningParameters, [_collectionView scrollableDirections], scrollDirection); -} - -@end -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASCollectionViewLayoutInspector.h b/submodules/AsyncDisplayKit/Source/Details/ASCollectionViewLayoutInspector.h deleted file mode 100644 index be812b68a3..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASCollectionViewLayoutInspector.h +++ /dev/null @@ -1,88 +0,0 @@ -// -// ASCollectionViewLayoutInspector.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import -#import - -@class ASCollectionView; -@protocol ASCollectionDataSource; -@protocol ASCollectionDelegate; - -NS_ASSUME_NONNULL_BEGIN - -AS_EXTERN ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView *collectionView); - -@protocol ASCollectionViewLayoutInspecting - -/** - * Asks the inspector to provide a constrained size range for the given collection view node. - */ -- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath; - -/** - * Return the directions in which your collection view can scroll - */ -- (ASScrollDirection)scrollableDirections; - -@optional - -/** - * Asks the inspector to provide a constrained size range for the given supplementary node. - */ -- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; - -/** - * Asks the inspector for the number of supplementary views for the given kind in the specified section. - */ -- (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section; - -/** - * Allow the inspector to respond to delegate changes. - * - * @discussion A great time to update perform selector caches! - */ -- (void)didChangeCollectionViewDelegate:(nullable id)delegate; - -/** - * Allow the inspector to respond to dataSource changes. - * - * @discussion A great time to update perform selector caches! - */ -- (void)didChangeCollectionViewDataSource:(nullable id)dataSource; - -#pragma mark Deprecated Methods - -/** - * Asks the inspector for the number of supplementary sections in the collection view for the given kind. - * - * @deprecated This method will not be called, and it is only deprecated as a reminder to remove it. - * Supplementary elements must exist in the same sections as regular collection view items i.e. -numberOfSectionsInCollectionView: - */ -- (NSUInteger)collectionView:(ASCollectionView *)collectionView numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead."); - -@end - -/** - * A layout inspector for non-flow layouts that returns a constrained size to let the cells layout itself as - * far as possible based on the scrollable direction of the collection view. - * It doesn't support supplementary nodes and therefore doesn't implement delegate methods - * that are related to supplementary node's management. - * - * @warning This class is not meant to be subclassed and will be restricted in the future. - */ -@interface ASCollectionViewLayoutInspector : NSObject -@end - - -NS_ASSUME_NONNULL_END - -#endif \ No newline at end of file diff --git a/submodules/AsyncDisplayKit/Source/Details/ASCollectionViewLayoutInspector.mm b/submodules/AsyncDisplayKit/Source/Details/ASCollectionViewLayoutInspector.mm deleted file mode 100644 index f02fe1c171..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASCollectionViewLayoutInspector.mm +++ /dev/null @@ -1,78 +0,0 @@ -// -// ASCollectionViewLayoutInspector.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// -#ifndef MINIMAL_ASDK -#import - -#import -#import -#import - -#pragma mark - Helper Functions - -// Returns a constrained size to let the cells layout itself as far as possible based on the scrollable direction -// of the collection view -ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView *collectionView) { - CGSize maxSize = collectionView.bounds.size; - UIEdgeInsets contentInset = collectionView.contentInset; - if (ASScrollDirectionContainsHorizontalDirection(collectionView.scrollableDirections)) { - maxSize.width = CGFLOAT_MAX; - maxSize.height -= (contentInset.top + contentInset.bottom); - } else { - maxSize.width -= (contentInset.left + contentInset.right); - maxSize.height = CGFLOAT_MAX; - } - return ASSizeRangeMake(CGSizeZero, maxSize); -} - -#pragma mark - ASCollectionViewLayoutInspector - -@implementation ASCollectionViewLayoutInspector { - struct { - unsigned int implementsConstrainedSizeForNodeAtIndexPathDeprecated:1; - unsigned int implementsConstrainedSizeForNodeAtIndexPath:1; - } _delegateFlags; -} - -#pragma mark ASCollectionViewLayoutInspecting - -- (void)didChangeCollectionViewDelegate:(id)delegate -{ - if (delegate == nil) { - memset(&_delegateFlags, 0, sizeof(_delegateFlags)); - } else { - _delegateFlags.implementsConstrainedSizeForNodeAtIndexPathDeprecated = [delegate respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)]; - _delegateFlags.implementsConstrainedSizeForNodeAtIndexPath = [delegate respondsToSelector:@selector(collectionNode:constrainedSizeForItemAtIndexPath:)]; - } -} - -- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath -{ - if (_delegateFlags.implementsConstrainedSizeForNodeAtIndexPath) { - return [collectionView.asyncDelegate collectionNode:collectionView.collectionNode constrainedSizeForItemAtIndexPath:indexPath]; - } else if (_delegateFlags.implementsConstrainedSizeForNodeAtIndexPathDeprecated) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [collectionView.asyncDelegate collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } else { - // With 2.0 `collectionView:constrainedSizeForNodeAtIndexPath:` was moved to the delegate. Assert if not implemented on the delegate but on the data source - ASDisplayNodeAssert([collectionView.asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)] == NO, @"collectionView:constrainedSizeForNodeAtIndexPath: was moved from the ASCollectionDataSource to the ASCollectionDelegate."); - } - - return NodeConstrainedSizeForScrollDirection(collectionView); -} - -- (ASScrollDirection)scrollableDirections -{ - return ASScrollDirectionNone; -} - -@end - -#endif \ No newline at end of file diff --git a/submodules/AsyncDisplayKit/Source/Details/ASDataController.h b/submodules/AsyncDisplayKit/Source/Details/ASDataController.h deleted file mode 100644 index 5a03f24e23..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASDataController.h +++ /dev/null @@ -1,291 +0,0 @@ -// -// ASDataController.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK -#pragma once - -#import -#import -#import -#import -#ifdef __cplusplus -#import -#endif - -NS_ASSUME_NONNULL_BEGIN - -#if ASEVENTLOG_ENABLE -#define ASDataControllerLogEvent(dataController, ...) [dataController.eventLog logEventWithBacktrace:(AS_SAVE_EVENT_BACKTRACES ? [NSThread callStackSymbols] : nil) format:__VA_ARGS__] -#else -#define ASDataControllerLogEvent(dataController, ...) -#endif - -@class ASCellNode; -@class ASCollectionElement; -@class ASCollectionLayoutContext; -@class ASCollectionLayoutState; -@class ASDataController; -@class ASElementMap; -@class ASLayout; -@class _ASHierarchyChangeSet; -@protocol ASRangeManagingNode; -@protocol ASTraitEnvironment; -@protocol ASSectionContext; - -typedef NSUInteger ASDataControllerAnimationOptions; - -AS_EXTERN NSString * const ASDataControllerRowNodeKind; -AS_EXTERN NSString * const ASCollectionInvalidUpdateException; - -/** - Data source for data controller - It will be invoked in the same thread as the api call of ASDataController. - */ - -@protocol ASDataControllerSource - -/** - Fetch the ASCellNode block for specific index path. This block should return the ASCellNode for the specified index path. - */ -- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath shouldAsyncLayout:(BOOL *)shouldAsyncLayout; - -/** - Fetch the number of rows in specific section. - */ -- (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section; - -/** - Fetch the number of sections. - */ -- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController; - -/** - Returns if the collection element size matches a given size. - @precondition The element is present in the data controller's visible map. - */ -- (BOOL)dataController:(ASDataController *)dataController presentedSizeForElement:(ASCollectionElement *)element matchesSize:(CGSize)size; - -- (nullable id)dataController:(ASDataController *)dataController nodeModelForItemAtIndexPath:(NSIndexPath *)indexPath; - -/** - * Called just after dispatching ASCellNode allocation and layout to the concurrent background queue. - * In some cases, for example on the first content load for a screen, it may be desirable to call - * -waitUntilAllUpdatesAreProcessed at this point. - * - * Returning YES will cause the ASDataController to wait on the background queue, and this ensures - * that any new / changed cells are in the hierarchy by the very next CATransaction / frame draw. - */ -- (BOOL)dataController:(ASDataController *)dataController shouldSynchronouslyProcessChangeSet:(_ASHierarchyChangeSet *)changeSet; -- (BOOL)dataController:(ASDataController *)dataController shouldEagerlyLayoutNode:(ASCellNode *)node; -- (BOOL)dataControllerShouldSerializeNodeCreation:(ASDataController *)dataController; - -@optional - -/** - The constrained size range for layout. Called only if collection layout delegate is not provided. - */ -- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath; - -- (NSArray *)dataController:(ASDataController *)dataController supplementaryNodeKindsInSections:(NSIndexSet *)sections; - -- (NSUInteger)dataController:(ASDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section; - -- (ASCellNodeBlock)dataController:(ASDataController *)dataController supplementaryNodeBlockOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath shouldAsyncLayout:(BOOL *)shouldAsyncLayout; - -/** - The constrained size range for layout. Called only if no data controller layout delegate is provided. - */ -- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; - -- (nullable id)dataController:(ASDataController *)dataController contextForSection:(NSInteger)section; - -@end - -/** - Delegate for notify the data updating of data controller. - These methods will be invoked from main thread right now, but it may be moved to background thread in the future. - */ -@protocol ASDataControllerDelegate - -/** - * Called for change set updates. - * - * @param changeSet The change set that includes all updates - * - * @param updates The block that performs relevant data updates. - * - * @discussion The updates block must always be executed or the data controller will get into a bad state. - * It should be called at the time the backing view is ready to process the updates, - * i.e inside the updates block of `-[UICollectionView performBatchUpdates:completion:] or after calling `-[UITableView beginUpdates]`. - */ -- (void)dataController:(ASDataController *)dataController updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet updates:(dispatch_block_t)updates; - -@end - -@protocol ASDataControllerLayoutDelegate - -/** - * @abstract Returns a layout context needed for a coming layout pass with the given elements. - * The context should contain the elements and any additional information needed. - * - * @discussion This method will be called on main thread. - */ -- (ASCollectionLayoutContext *)layoutContextWithElements:(ASElementMap *)elements; - -/** - * @abstract Prepares and returns a new layout for given context. - * - * @param context A context that was previously returned by `-layoutContextWithElements:`. - * - * @return The new layout calculated for the given context. - * - * @discussion This method is called ahead of time, i.e before the underlying collection/table view is aware of the provided elements. - * As a result, clients must solely rely on the given context and should not reach out to other objects for information not available in the context. - * - * This method will be called on background theads. It must be thread-safe and should not change any internal state of the conforming object. - * It must block the calling thread but can dispatch to other theads to reduce total blocking time. - */ -+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context; - -@end - -/** - * Controller to layout data in background, and managed data updating. - * - * All operations are asynchronous and thread safe. You can call it from background thread (it is recommendated) and the data - * will be updated asynchronously. The dataSource must be updated to reflect the changes before these methods has been called. - * For each data updating, the corresponding methods in delegate will be called. - */ -@interface ASDataController : NSObject - -- (instancetype)initWithDataSource:(id)dataSource node:(nullable id)node eventLog:(nullable ASEventLog *)eventLog NS_DESIGNATED_INITIALIZER; - -- (instancetype)init NS_UNAVAILABLE; - -/** - * The node that owns this data controller, if any. - * - * NOTE: Soon we will drop support for using ASTableView/ASCollectionView without the node, so this will be non-null. - */ -@property (nullable, nonatomic, weak, readonly) id node; - -/** - * The map that is currently displayed. The "UIKit index space." - * - * This property will only be changed on the main thread. - */ -@property (copy, readonly) ASElementMap *visibleMap; - -/** - * The latest map fetched from the data source. May be more recent than @c visibleMap. - * - * This property will only be changed on the main thread. - */ -@property (copy, readonly) ASElementMap *pendingMap; - -/** - Data source for fetching data info. - */ -@property (nonatomic, weak, readonly) id dataSource; - -/** - An object that will be included in the backtrace of any update validation exceptions that occur. - */ -@property (nonatomic, weak) id validationErrorSource; - -/** - Delegate to notify when data is updated. - */ -@property (nonatomic, weak) id delegate; - -/** - * Delegate for preparing layouts. Main thead only. - */ -@property (nonatomic, weak) id layoutDelegate; - -#ifdef __cplusplus -/** - * Returns the most recently gathered item counts from the data source. If the counts - * have been invalidated, this synchronously queries the data source and saves the result. - * - * This must be called on the main thread. - */ -- (std::vector)itemCountsFromDataSource; -#endif - -/** - * Returns YES if reloadData has been called at least once. Before this point it is - * important to ignore/suppress some operations. For example, inserting a section - * before the initial data load should have no effect. - * - * This must be called on the main thread. - */ -@property (nonatomic, readonly) BOOL initialReloadDataHasBeenCalled; - -#if ASEVENTLOG_ENABLE -/* - * @abstract The primitive event tracing object. You shouldn't directly use it to log event. Use the ASDataControllerLogEvent macro instead. - */ -@property (nonatomic, readonly) ASEventLog *eventLog; -#endif - -/** @name Data Updating */ - -- (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet; - -/** - * Re-measures all loaded nodes in the backing store. - * - * @discussion Used to respond to a change in size of the containing view - * (e.g. ASTableView or ASCollectionView after an orientation change). - * - * The invalidationBlock is called after flushing the ASMainSerialQueue, which ensures that any in-progress - * layout calculations have been applied. The block will not be called if data hasn't been loaded. - */ -- (void)relayoutAllNodesWithInvalidationBlock:(nullable void (^)(void))invalidationBlock; - -/** - * Re-measures given nodes in the backing store. - * - * @discussion Used to respond to setNeedsLayout calls in ASCellNode - */ -- (void)relayoutNodes:(id)nodes nodesSizeChanged:(NSMutableArray *)nodesSizesChanged; - -/** - * See ASCollectionNode.h for full documentation of these methods. - */ -@property (nonatomic, readonly) BOOL isProcessingUpdates; -- (void)onDidFinishProcessingUpdates:(void (^)(void))completion; -- (void)waitUntilAllUpdatesAreProcessed; - -/** - * See ASCollectionNode.h for full documentation of these methods. - */ -@property (nonatomic, readonly, getter=isSynchronized) BOOL synchronized; -- (void)onDidFinishSynchronizing:(void (^)(void))completion; - -/** - * Notifies the data controller object that its environment has changed. The object will request its environment delegate for new information - * and propagate the information to all visible elements, including ones that are being prepared in background. - * - * @discussion If called before the initial @c reloadData, this method will do nothing and the trait collection of the initial load will be requested from the environment delegate. - * - * @discussion This method can be called on any threads. - */ -- (void)environmentDidChange; - -/** - * Reset visibleMap and pendingMap when asyncDataSource and asyncDelegate of collection view become nil. - */ -- (void)clearData; - -@end - -NS_ASSUME_NONNULL_END -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASDataController.mm b/submodules/AsyncDisplayKit/Source/Details/ASDataController.mm deleted file mode 100644 index a842e76c44..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASDataController.mm +++ /dev/null @@ -1,958 +0,0 @@ -// -// ASDataController.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -#include - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#import -#import -#import -#import - -//#define LOG(...) NSLog(__VA_ARGS__) -#define LOG(...) - -#define ASSERT_ON_EDITING_QUEUE ASDisplayNodeAssertNotNil(dispatch_get_specific(&kASDataControllerEditingQueueKey), @"%@ must be called on the editing transaction queue.", NSStringFromSelector(_cmd)) - -const static char * kASDataControllerEditingQueueKey = "kASDataControllerEditingQueueKey"; -const static char * kASDataControllerEditingQueueContext = "kASDataControllerEditingQueueContext"; - -NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; -NSString * const ASCollectionInvalidUpdateException = @"ASCollectionInvalidUpdateException"; - -typedef dispatch_block_t ASDataControllerCompletionBlock; - -typedef void (^ASDataControllerSynchronizationBlock)(); - -@interface ASDataController () { - id _layoutDelegate; - - NSInteger _nextSectionID; - - BOOL _itemCountsFromDataSourceAreValid; // Main thread only. - std::vector _itemCountsFromDataSource; // Main thread only. - - ASMainSerialQueue *_mainSerialQueue; - - dispatch_queue_t _editingTransactionQueue; // Serial background queue. Dispatches concurrent layout and manages _editingNodes. - dispatch_group_t _editingTransactionGroup; // Group of all edit transaction blocks. Useful for waiting. - std::atomic _editingTransactionGroupCount; - - BOOL _initialReloadDataHasBeenCalled; - - BOOL _synchronized; - NSMutableSet *_onDidFinishSynchronizingBlocks; - - struct { - unsigned int supplementaryNodeKindsInSections:1; - unsigned int supplementaryNodesOfKindInSection:1; - unsigned int supplementaryNodeBlockOfKindAtIndexPath:1; - unsigned int constrainedSizeForNodeAtIndexPath:1; - unsigned int constrainedSizeForSupplementaryNodeOfKindAtIndexPath:1; - unsigned int contextForSection:1; - } _dataSourceFlags; -} - -@property (copy) ASElementMap *pendingMap; -@property (copy) ASElementMap *visibleMap; -@end - -@implementation ASDataController - -#pragma mark - Lifecycle - -- (instancetype)initWithDataSource:(id)dataSource node:(nullable id)node eventLog:(ASEventLog *)eventLog -{ - if (!(self = [super init])) { - return nil; - } - - _node = node; - _dataSource = dataSource; - - _dataSourceFlags.supplementaryNodeKindsInSections = [_dataSource respondsToSelector:@selector(dataController:supplementaryNodeKindsInSections:)]; - _dataSourceFlags.supplementaryNodesOfKindInSection = [_dataSource respondsToSelector:@selector(dataController:supplementaryNodesOfKind:inSection:)]; - _dataSourceFlags.supplementaryNodeBlockOfKindAtIndexPath = [_dataSource respondsToSelector:@selector(dataController:supplementaryNodeBlockOfKind:atIndexPath:shouldAsyncLayout:)]; - _dataSourceFlags.constrainedSizeForNodeAtIndexPath = [_dataSource respondsToSelector:@selector(dataController:constrainedSizeForNodeAtIndexPath:)]; - _dataSourceFlags.constrainedSizeForSupplementaryNodeOfKindAtIndexPath = [_dataSource respondsToSelector:@selector(dataController:constrainedSizeForSupplementaryNodeOfKind:atIndexPath:)]; - _dataSourceFlags.contextForSection = [_dataSource respondsToSelector:@selector(dataController:contextForSection:)]; - -#if ASEVENTLOG_ENABLE - _eventLog = eventLog; -#endif - - self.visibleMap = self.pendingMap = [[ASElementMap alloc] init]; - - _nextSectionID = 0; - - _mainSerialQueue = [[ASMainSerialQueue alloc] init]; - - _synchronized = YES; - _onDidFinishSynchronizingBlocks = [[NSMutableSet alloc] init]; - - const char *queueName = [[NSString stringWithFormat:@"org.AsyncDisplayKit.ASDataController.editingTransactionQueue:%p", self] cStringUsingEncoding:NSASCIIStringEncoding]; - _editingTransactionQueue = dispatch_queue_create(queueName, DISPATCH_QUEUE_SERIAL); - dispatch_queue_set_specific(_editingTransactionQueue, &kASDataControllerEditingQueueKey, &kASDataControllerEditingQueueContext, NULL); - _editingTransactionGroup = dispatch_group_create(); - - return self; -} - -- (id)layoutDelegate -{ - ASDisplayNodeAssertMainThread(); - return _layoutDelegate; -} - -- (void)setLayoutDelegate:(id)layoutDelegate -{ - ASDisplayNodeAssertMainThread(); - if (layoutDelegate != _layoutDelegate) { - _layoutDelegate = layoutDelegate; - } -} - -#pragma mark - Cell Layout - -- (void)_allocateNodesFromElements:(NSArray *)elements -{ - ASSERT_ON_EDITING_QUEUE; - - NSUInteger nodeCount = elements.count; - __weak id weakDataSource = _dataSource; - if (nodeCount == 0 || weakDataSource == nil) { - return; - } - - ASSignpostStart(ASSignpostDataControllerBatch); - - { - as_activity_create_for_scope("Data controller batch"); - - dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0); - NSUInteger threadCount = 0; - if ([_dataSource dataControllerShouldSerializeNodeCreation:self]) { - threadCount = 1; - } - ASDispatchApply(nodeCount, queue, threadCount, ^(size_t i) { - __strong id strongDataSource = weakDataSource; - if (strongDataSource == nil) { - return; - } - - unowned ASCollectionElement *element = elements[i]; - - NSMutableDictionary *dict = [[NSThread currentThread] threadDictionary]; - dict[ASThreadDictMaxConstraintSizeKey] = - [NSValue valueWithCGSize:element.constrainedSize.max]; - unowned ASCellNode *node = element.node; - [dict removeObjectForKey:ASThreadDictMaxConstraintSizeKey]; - - // Layout the node if the size range is valid. - ASSizeRange sizeRange = element.constrainedSize; - if (ASSizeRangeHasSignificantArea(sizeRange)) { - [self _layoutNode:node withConstrainedSize:sizeRange]; - } - }); - } - - ASSignpostEndCustom(ASSignpostDataControllerBatch, self, 0, (weakDataSource != nil ? ASSignpostColorDefault : ASSignpostColorRed)); -} - -/** - * Measure and layout the given node with the constrained size range. - */ -- (void)_layoutNode:(ASCellNode *)node withConstrainedSize:(ASSizeRange)constrainedSize -{ - if (![_dataSource dataController:self shouldEagerlyLayoutNode:node]) { - return; - } - - ASDisplayNodeAssert(ASSizeRangeHasSignificantArea(constrainedSize), @"Attempt to layout cell node with invalid size range %@", NSStringFromASSizeRange(constrainedSize)); - - CGRect frame = CGRectZero; - frame.size = [node layoutThatFits:constrainedSize].size; - node.frame = frame; -} - -#pragma mark - Data Source Access (Calling _dataSource) - -- (NSArray *)_allIndexPathsForItemsOfKind:(NSString *)kind inSections:(NSIndexSet *)sections -{ - ASDisplayNodeAssertMainThread(); - - if (sections.count == 0 || _dataSource == nil) { - return @[]; - } - - const auto indexPaths = [[NSMutableArray alloc] init]; - if ([kind isEqualToString:ASDataControllerRowNodeKind]) { - std::vector counts = [self itemCountsFromDataSource]; - [sections enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { - for (NSUInteger sectionIndex = range.location; sectionIndex < NSMaxRange(range); sectionIndex++) { - NSUInteger itemCount = counts[sectionIndex]; - for (NSUInteger i = 0; i < itemCount; i++) { - [indexPaths addObject:[NSIndexPath indexPathForItem:i inSection:sectionIndex]]; - } - } - }]; - } else if (_dataSourceFlags.supplementaryNodesOfKindInSection) { - id dataSource = _dataSource; - [sections enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { - for (NSUInteger sectionIndex = range.location; sectionIndex < NSMaxRange(range); sectionIndex++) { - NSUInteger itemCount = [dataSource dataController:self supplementaryNodesOfKind:kind inSection:sectionIndex]; - for (NSUInteger i = 0; i < itemCount; i++) { - [indexPaths addObject:[NSIndexPath indexPathForItem:i inSection:sectionIndex]]; - } - } - }]; - } - - return indexPaths; -} - -/** - * Agressively repopulates supplementary nodes of all kinds for sections that contains some given index paths. - * - * @param map The element map into which to apply the change. - * @param indexPaths The index paths belongs to sections whose supplementary nodes need to be repopulated. - * @param changeSet The changeset that triggered this repopulation. - * @param traitCollection The trait collection needed to initialize elements - * @param indexPathsAreNew YES if index paths are "after the update," NO otherwise. - * @param shouldFetchSizeRanges Whether constrained sizes should be fetched from data source - */ -- (void)_repopulateSupplementaryNodesIntoMap:(ASMutableElementMap *)map - forSectionsContainingIndexPaths:(NSArray *)indexPaths - changeSet:(_ASHierarchyChangeSet *)changeSet - traitCollection:(ASPrimitiveTraitCollection)traitCollection - indexPathsAreNew:(BOOL)indexPathsAreNew - shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges - previousMap:(ASElementMap *)previousMap -{ - ASDisplayNodeAssertMainThread(); - - if (indexPaths.count == 0) { - return; - } - - // Remove all old supplementaries from these sections - NSIndexSet *oldSections = [NSIndexSet as_sectionsFromIndexPaths:indexPaths]; - - // Add in new ones with the new kinds. - NSIndexSet *newSections; - if (indexPathsAreNew) { - newSections = oldSections; - } else { - newSections = [oldSections as_indexesByMapping:^NSUInteger(NSUInteger oldSection) { - return [changeSet newSectionForOldSection:oldSection]; - }]; - } - - for (NSString *kind in [self supplementaryKindsInSections:newSections]) { - [self _insertElementsIntoMap:map kind:kind forSections:newSections traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges changeSet:changeSet previousMap:previousMap]; - } -} - -/** - * Update supplementary nodes of all kinds for sections. - * - * @param map The element map into which to apply the change. - * @param traitCollection The trait collection needed to initialize elements - * @param shouldFetchSizeRanges Whether constrained sizes should be fetched from data source - */ -- (void)_updateSupplementaryNodesIntoMap:(ASMutableElementMap *)map - traitCollection:(ASPrimitiveTraitCollection)traitCollection - shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges - previousMap:(ASElementMap *)previousMap -{ - ASDisplayNodeAssertMainThread(); - if (self.layoutDelegate != nil) { - // TODO: https://github.com/TextureGroup/Texture/issues/948 - return; - } - NSUInteger sectionCount = [self itemCountsFromDataSource].size(); - if (sectionCount > 0) { - NSIndexSet *sectionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)]; - ASSizeRange newSizeRange = ASSizeRangeZero; - for (NSString *kind in [self supplementaryKindsInSections:sectionIndexes]) { - NSArray *indexPaths = [self _allIndexPathsForItemsOfKind:kind inSections:sectionIndexes]; - NSMutableArray *indexPathsToDeleteForKind = [[NSMutableArray alloc] init]; - NSMutableArray *indexPathsToInsertForKind = [[NSMutableArray alloc] init]; - // If supplementary node does exist and size is now zero, remove it. - // If supplementary node doesn't exist and size is now non-zero, insert one. - for (NSIndexPath *indexPath in indexPaths) { - ASCollectionElement *previousElement = [previousMap supplementaryElementOfKind:kind atIndexPath:indexPath]; - newSizeRange = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; - BOOL sizeRangeIsZero = ASSizeRangeEqualToSizeRange(ASSizeRangeZero, newSizeRange); - if (previousElement != nil && sizeRangeIsZero) { - [indexPathsToDeleteForKind addObject:indexPath]; - } else if (previousElement == nil && !sizeRangeIsZero) { - [indexPathsToInsertForKind addObject:indexPath]; - } - } - - [map removeSupplementaryElementsAtIndexPaths:indexPathsToDeleteForKind kind:kind]; - [self _insertElementsIntoMap:map kind:kind atIndexPaths:indexPathsToInsertForKind traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges changeSet:nil previousMap:previousMap]; - } - } -} - -/** - * Inserts new elements of a certain kind for some sections - * - * @param kind The kind of the elements, e.g ASDataControllerRowNodeKind - * @param sections The sections that should be populated by new elements - * @param traitCollection The trait collection needed to initialize elements - * @param shouldFetchSizeRanges Whether constrained sizes should be fetched from data source - */ -- (void)_insertElementsIntoMap:(ASMutableElementMap *)map - kind:(NSString *)kind - forSections:(NSIndexSet *)sections - traitCollection:(ASPrimitiveTraitCollection)traitCollection - shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges - changeSet:(_ASHierarchyChangeSet *)changeSet - previousMap:(ASElementMap *)previousMap -{ - ASDisplayNodeAssertMainThread(); - - if (sections.count == 0 || _dataSource == nil) { - return; - } - - NSArray *indexPaths = [self _allIndexPathsForItemsOfKind:kind inSections:sections]; - [self _insertElementsIntoMap:map kind:kind atIndexPaths:indexPaths traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges changeSet:changeSet previousMap:previousMap]; -} - -/** - * Inserts new elements of a certain kind at some index paths - * - * @param map The map to insert the elements into. - * @param kind The kind of the elements, e.g ASDataControllerRowNodeKind - * @param indexPaths The index paths at which new elements should be populated - * @param traitCollection The trait collection needed to initialize elements - * @param shouldFetchSizeRanges Whether constrained sizes should be fetched from data source - */ -- (void)_insertElementsIntoMap:(ASMutableElementMap *)map - kind:(NSString *)kind - atIndexPaths:(NSArray *)indexPaths - traitCollection:(ASPrimitiveTraitCollection)traitCollection - shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges - changeSet:(_ASHierarchyChangeSet *)changeSet - previousMap:(ASElementMap *)previousMap -{ - ASDisplayNodeAssertMainThread(); - - if (indexPaths.count == 0 || _dataSource == nil) { - return; - } - - BOOL isRowKind = [kind isEqualToString:ASDataControllerRowNodeKind]; - if (!isRowKind && !_dataSourceFlags.supplementaryNodeBlockOfKindAtIndexPath) { - // Populating supplementary elements but data source doesn't support. - return; - } - - LOG(@"Populating elements of kind: %@, for index paths: %@", kind, indexPaths); - id dataSource = self.dataSource; - id node = self.node; - BOOL shouldAsyncLayout = YES; - for (NSIndexPath *indexPath in indexPaths) { - ASCellNodeBlock nodeBlock; - id nodeModel; - if (isRowKind) { - nodeModel = [dataSource dataController:self nodeModelForItemAtIndexPath:indexPath]; - - // Get the prior element and attempt to update the existing cell node. - if (nodeModel != nil && !changeSet.includesReloadData) { - NSIndexPath *oldIndexPath = [changeSet oldIndexPathForNewIndexPath:indexPath]; - if (oldIndexPath != nil) { - ASCollectionElement *oldElement = [previousMap elementForItemAtIndexPath:oldIndexPath]; - ASCellNode *oldNode = oldElement.node; - if ([oldNode canUpdateToNodeModel:nodeModel]) { - // Just wrap the node in a block. The collection element will -setNodeModel: - nodeBlock = ^{ - return oldNode; - }; - } - } - } - if (nodeBlock == nil) { - nodeBlock = [dataSource dataController:self nodeBlockAtIndexPath:indexPath shouldAsyncLayout:&shouldAsyncLayout]; - } - } else { - nodeBlock = [dataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath shouldAsyncLayout:&shouldAsyncLayout]; - } - - ASSizeRange constrainedSize = ASSizeRangeUnconstrained; - if (shouldFetchSizeRanges) { - constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; - } - - ASCollectionElement *element = [[ASCollectionElement alloc] initWithNodeModel:nodeModel - nodeBlock:nodeBlock - supplementaryElementKind:isRowKind ? nil : kind - constrainedSize:constrainedSize - owningNode:node - traitCollection:traitCollection]; - [map insertElement:element atIndexPath:indexPath]; - changeSet.countForAsyncLayout += (shouldAsyncLayout ? 1 : 0); - } -} - -- (void)invalidateDataSourceItemCounts -{ - ASDisplayNodeAssertMainThread(); - _itemCountsFromDataSourceAreValid = NO; -} - -- (std::vector)itemCountsFromDataSource -{ - ASDisplayNodeAssertMainThread(); - if (NO == _itemCountsFromDataSourceAreValid) { - id source = self.dataSource; - NSInteger sectionCount = [source numberOfSectionsInDataController:self]; - std::vector newCounts; - newCounts.reserve(sectionCount); - for (NSInteger i = 0; i < sectionCount; i++) { - newCounts.push_back([source dataController:self rowsInSection:i]); - } - _itemCountsFromDataSource = newCounts; - _itemCountsFromDataSourceAreValid = YES; - } - return _itemCountsFromDataSource; -} - -- (NSArray *)supplementaryKindsInSections:(NSIndexSet *)sections -{ - if (_dataSourceFlags.supplementaryNodeKindsInSections) { - return [_dataSource dataController:self supplementaryNodeKindsInSections:sections]; - } - - return @[]; -} - -/** - * Returns constrained size for the node of the given kind and at the given index path. - * NOTE: index path must be in the data-source index space. - */ -- (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertMainThread(); - - id dataSource = _dataSource; - if (dataSource == nil || indexPath == nil) { - return ASSizeRangeZero; - } - - if ([kind isEqualToString:ASDataControllerRowNodeKind]) { - ASDisplayNodeAssert(_dataSourceFlags.constrainedSizeForNodeAtIndexPath, @"-dataController:constrainedSizeForNodeAtIndexPath: must also be implemented"); - return [dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPath]; - } - - if (_dataSourceFlags.constrainedSizeForSupplementaryNodeOfKindAtIndexPath){ - return [dataSource dataController:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:indexPath]; - } - - ASDisplayNodeAssert(NO, @"Unknown constrained size for node of kind %@ by data source %@", kind, dataSource); - return ASSizeRangeZero; -} - -#pragma mark - Batching (External API) - -- (void)waitUntilAllUpdatesAreProcessed -{ - // Schedule block in main serial queue to wait until all operations are finished that are - // where scheduled while waiting for the _editingTransactionQueue to finish - [self _scheduleBlockOnMainSerialQueue:^{ }]; -} - -- (BOOL)isProcessingUpdates -{ - ASDisplayNodeAssertMainThread(); - return _mainSerialQueue.numberOfScheduledBlocks > 0 || _editingTransactionGroupCount > 0; -} - -- (void)onDidFinishProcessingUpdates:(void (^)())completion -{ - ASDisplayNodeAssertMainThread(); - if (!completion) { - return; - } - if ([self isProcessingUpdates] == NO) { - ASPerformBlockOnMainThread(completion); - } else { - dispatch_async(_editingTransactionQueue, ^{ - // Retry the block. If we're done processing updates, it'll run immediately, otherwise - // wait again for updates to quiesce completely. - // Don't use _mainSerialQueue so that we don't affect -isProcessingUpdates. - dispatch_async(dispatch_get_main_queue(), ^{ - [self onDidFinishProcessingUpdates:completion]; - }); - }); - } -} - -- (BOOL)isSynchronized { - return _synchronized; -} - -- (void)onDidFinishSynchronizing:(void (^)())completion { - ASDisplayNodeAssertMainThread(); - if (!completion) { - return; - } - if ([self isSynchronized]) { - ASPerformBlockOnMainThread(completion); - } else { - // Hang on to the completion block so that it gets called the next time view is synchronized to data. - [_onDidFinishSynchronizingBlocks addObject:[completion copy]]; - } -} - -- (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet -{ - ASDisplayNodeAssertMainThread(); - - _synchronized = NO; - - [changeSet addCompletionHandler:^(BOOL finished) { - _synchronized = YES; - [self onDidFinishProcessingUpdates:^{ - if (_synchronized) { - for (ASDataControllerSynchronizationBlock block in _onDidFinishSynchronizingBlocks) { - block(); - } - [_onDidFinishSynchronizingBlocks removeAllObjects]; - } - }]; - }]; - - if (changeSet.includesReloadData) { - if (_initialReloadDataHasBeenCalled) { - as_log_debug(ASCollectionLog(), "reloadData %@", ASViewToDisplayNode(ASDynamicCast(self.dataSource, UIView))); - } else { - as_log_debug(ASCollectionLog(), "Initial reloadData %@", ASViewToDisplayNode(ASDynamicCast(self.dataSource, UIView))); - _initialReloadDataHasBeenCalled = YES; - } - } else { - as_log_debug(ASCollectionLog(), "performBatchUpdates %@ %@", ASViewToDisplayNode(ASDynamicCast(self.dataSource, UIView)), changeSet); - } - - NSTimeInterval transactionQueueFlushDuration = 0.0f; - { - AS::ScopeTimer t(transactionQueueFlushDuration); - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - } - - // If the initial reloadData has not been called, just bail because we don't have our old data source counts. - // See ASUICollectionViewTests.testThatIssuingAnUpdateBeforeInitialReloadIsUnacceptable - // for the issue that UICollectionView has that we're choosing to workaround. - if (!_initialReloadDataHasBeenCalled) { - as_log_debug(ASCollectionLog(), "%@ Skipped update because load hasn't happened.", ASObjectDescriptionMakeTiny(_dataSource)); - [changeSet executeCompletionHandlerWithFinished:YES]; - return; - } - - [self invalidateDataSourceItemCounts]; - - // Log events -#if ASEVENTLOG_ENABLE - ASDataControllerLogEvent(self, @"updateWithChangeSet waited on previous update for %fms. changeSet: %@", - transactionQueueFlushDuration * 1000.0f, changeSet); - NSTimeInterval changeSetStartTime = CACurrentMediaTime(); - NSString *changeSetDescription = ASObjectDescriptionMakeTiny(changeSet); - [changeSet addCompletionHandler:^(BOOL finished) { - ASDataControllerLogEvent(self, @"finishedUpdate in %fms: %@", - (CACurrentMediaTime() - changeSetStartTime) * 1000.0f, changeSetDescription); - }]; -#endif - - // Attempt to mark the update completed. This is when update validation will occur inside the changeset. - // If an invalid update exception is thrown, we catch it and inject our "validationErrorSource" object, - // which is the table/collection node's data source, into the exception reason to help debugging. - @try { - [changeSet markCompletedWithNewItemCounts:[self itemCountsFromDataSource]]; - } @catch (NSException *e) { - id responsibleDataSource = self.validationErrorSource; - if (e.name == ASCollectionInvalidUpdateException && responsibleDataSource != nil) { - [NSException raise:ASCollectionInvalidUpdateException format:@"%@: %@", [responsibleDataSource class], e.reason]; - } else { - @throw e; - } - } - - BOOL canDelegate = (self.layoutDelegate != nil); - ASElementMap *newMap; - ASCollectionLayoutContext *layoutContext; - { - as_activity_scope(as_activity_create("Latch new data for collection update", changeSet.rootActivity, OS_ACTIVITY_FLAG_DEFAULT)); - - // Step 1: Populate a new map that reflects the data source's state and use it as pendingMap - ASElementMap *previousMap = self.pendingMap; - if (changeSet.isEmpty) { - // If the change set is empty, nothing has changed so we can just reuse the previous map - newMap = previousMap; - } else { - // Mutable copy of current data. - ASMutableElementMap *mutableMap = [previousMap mutableCopy]; - - // Step 1.1: Update the mutable copies to match the data source's state - [self _updateSectionsInMap:mutableMap changeSet:changeSet]; - ASPrimitiveTraitCollection existingTraitCollection = [self.node primitiveTraitCollection]; - [self _updateElementsInMap:mutableMap changeSet:changeSet traitCollection:existingTraitCollection shouldFetchSizeRanges:(! canDelegate) previousMap:previousMap]; - - // Step 1.2: Clone the new data - newMap = [mutableMap copy]; - } - self.pendingMap = newMap; - - // Step 2: Ask layout delegate for contexts - if (canDelegate) { - layoutContext = [self.layoutDelegate layoutContextWithElements:newMap]; - } - } - - as_log_debug(ASCollectionLog(), "New content: %@", newMap.smallDescription); - - Class layoutDelegateClass = [self.layoutDelegate class]; - ++_editingTransactionGroupCount; - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - __block __unused os_activity_scope_state_s preparationScope = {}; // unused if deployment target < iOS10 - as_activity_scope_enter(as_activity_create("Prepare nodes for collection update", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT), &preparationScope); - - // Step 3: Call the layout delegate if possible. Otherwise, allocate and layout all elements - if (canDelegate) { - [layoutDelegateClass calculateLayoutWithContext:layoutContext]; - } else { - const auto elementsToProcess = [[NSMutableArray alloc] init]; - for (ASCollectionElement *element in newMap) { - ASCellNode *nodeIfAllocated = element.nodeIfAllocated; - if (nodeIfAllocated.shouldUseUIKitCell) { - // If the node exists and we know it is a passthrough cell, we know it will never have a .calculatedLayout. - continue; - } else if (nodeIfAllocated.calculatedLayout == nil) { - // If the node hasn't been allocated, or it doesn't have a valid layout, let's process it. - [elementsToProcess addObject:element]; - } - } - [self _allocateNodesFromElements:elementsToProcess]; - } - - // Step 4: Inform the delegate on main thread - [_mainSerialQueue performBlockOnMainThread:^{ - as_activity_scope_leave(&preparationScope); - [_delegate dataController:self updateWithChangeSet:changeSet updates:^{ - // Step 5: Deploy the new data as "completed" - // - // Note that since the backing collection view might be busy responding to user events (e.g scrolling), - // it will not consume the batch update blocks immediately. - // As a result, in a short intermidate time, the view will still be relying on the old data source state. - // Thus, we can't just swap the new map immediately before step 4, but until this update block is executed. - // (https://github.com/TextureGroup/Texture/issues/378) - self.visibleMap = newMap; - }]; - }]; - --_editingTransactionGroupCount; - }); - - // We've now dispatched node allocation and layout to a concurrent background queue. - // In some cases, it's advantageous to prevent the main thread from returning, to ensure the next - // frame displayed to the user has the view updates in place. Doing this does slightly reduce - // total latency, by donating the main thread's priority to the background threads. As such, the - // two cases where it makes sense to block: - // 1. There is very little work to be performed in the background (UIKit passthrough) - // 2. There is a higher priority on display latency than smoothness, e.g. app startup. - if ([_dataSource dataController:self shouldSynchronouslyProcessChangeSet:changeSet]) { - [self waitUntilAllUpdatesAreProcessed]; - } -} - -/** - * Update sections based on the given change set. - */ -- (void)_updateSectionsInMap:(ASMutableElementMap *)map changeSet:(_ASHierarchyChangeSet *)changeSet -{ - ASDisplayNodeAssertMainThread(); - - if (changeSet.includesReloadData) { - [map removeAllSections]; - - NSUInteger sectionCount = [self itemCountsFromDataSource].size(); - NSIndexSet *sectionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)]; - [self _insertSectionsIntoMap:map indexes:sectionIndexes]; - // Return immediately because reloadData can't be used in conjuntion with other updates. - return; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeDelete]) { - [map removeSectionsAtIndexes:change.indexSet]; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) { - [self _insertSectionsIntoMap:map indexes:change.indexSet]; - } -} - -- (void)_insertSectionsIntoMap:(ASMutableElementMap *)map indexes:(NSIndexSet *)sectionIndexes -{ - ASDisplayNodeAssertMainThread(); - - [sectionIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { - id context; - if (_dataSourceFlags.contextForSection) { - context = [_dataSource dataController:self contextForSection:idx]; - } - ASSection *section = [[ASSection alloc] initWithSectionID:_nextSectionID context:context]; - [map insertSection:section atIndex:idx]; - _nextSectionID++; - }]; -} - -/** - * Update elements based on the given change set. - */ -- (void)_updateElementsInMap:(ASMutableElementMap *)map - changeSet:(_ASHierarchyChangeSet *)changeSet - traitCollection:(ASPrimitiveTraitCollection)traitCollection - shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges - previousMap:(ASElementMap *)previousMap -{ - ASDisplayNodeAssertMainThread(); - - if (changeSet.includesReloadData) { - [map removeAllElements]; - - NSUInteger sectionCount = [self itemCountsFromDataSource].size(); - if (sectionCount > 0) { - NSIndexSet *sectionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)]; - [self _insertElementsIntoMap:map sections:sectionIndexes traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges changeSet:changeSet previousMap:previousMap]; - } - // Return immediately because reloadData can't be used in conjuntion with other updates. - return; - } - - // Migrate old supplementary nodes to their new index paths. - [map migrateSupplementaryElementsWithSectionMapping:changeSet.sectionMapping]; - - for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeDelete]) { - [map removeItemsAtIndexPaths:change.indexPaths]; - // Aggressively repopulate supplementary nodes (#1773 & #1629) - [self _repopulateSupplementaryNodesIntoMap:map forSectionsContainingIndexPaths:change.indexPaths - changeSet:changeSet - traitCollection:traitCollection - indexPathsAreNew:NO - shouldFetchSizeRanges:shouldFetchSizeRanges - previousMap:previousMap]; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeDelete]) { - NSIndexSet *sectionIndexes = change.indexSet; - [map removeSectionsOfItems:sectionIndexes]; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) { - [self _insertElementsIntoMap:map sections:change.indexSet traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges changeSet:changeSet previousMap:previousMap]; - } - - for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeInsert]) { - [self _insertElementsIntoMap:map kind:ASDataControllerRowNodeKind atIndexPaths:change.indexPaths traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges changeSet:changeSet previousMap:previousMap]; - // Aggressively reload supplementary nodes (#1773 & #1629) - [self _repopulateSupplementaryNodesIntoMap:map forSectionsContainingIndexPaths:change.indexPaths - changeSet:changeSet - traitCollection:traitCollection - indexPathsAreNew:YES - shouldFetchSizeRanges:shouldFetchSizeRanges - previousMap:previousMap]; - } -} - -- (void)_insertElementsIntoMap:(ASMutableElementMap *)map - sections:(NSIndexSet *)sectionIndexes - traitCollection:(ASPrimitiveTraitCollection)traitCollection - shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges - changeSet:(_ASHierarchyChangeSet *)changeSet - previousMap:(ASElementMap *)previousMap -{ - ASDisplayNodeAssertMainThread(); - - if (sectionIndexes.count == 0 || _dataSource == nil) { - return; - } - - // Items - [map insertEmptySectionsOfItemsAtIndexes:sectionIndexes]; - [self _insertElementsIntoMap:map kind:ASDataControllerRowNodeKind forSections:sectionIndexes traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges changeSet:changeSet previousMap:previousMap]; - - // Supplementaries - for (NSString *kind in [self supplementaryKindsInSections:sectionIndexes]) { - // Step 2: Populate new elements for all sections - [self _insertElementsIntoMap:map kind:kind forSections:sectionIndexes traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges changeSet:changeSet previousMap:previousMap]; - } -} - -#pragma mark - Relayout - -- (void)relayoutNodes:(id)nodes nodesSizeChanged:(NSMutableArray *)nodesSizesChanged -{ - NSParameterAssert(nodes); - NSParameterAssert(nodesSizesChanged); - - ASDisplayNodeAssertMainThread(); - if (!_initialReloadDataHasBeenCalled) { - return; - } - - id dataSource = self.dataSource; - const auto visibleMap = self.visibleMap; - const auto pendingMap = self.pendingMap; - for (ASCellNode *node in nodes) { - const auto element = node.collectionElement; - NSIndexPath *indexPathInPendingMap = [pendingMap indexPathForElement:element]; - // Ensure the element is present in both maps or skip it. If it's not in the visible map, - // then we can't check the presented size. If it's not in the pending map, we can't get the constrained size. - // This will only happen if the element has been deleted, so the specifics of this behavior aren't important. - if (indexPathInPendingMap == nil || [visibleMap indexPathForElement:element] == nil) { - continue; - } - - NSString *kind = element.supplementaryElementKind ?: ASDataControllerRowNodeKind; - ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPathInPendingMap]; - [self _layoutNode:node withConstrainedSize:constrainedSize]; - - BOOL matchesSize = [dataSource dataController:self presentedSizeForElement:element matchesSize:node.frame.size]; - if (! matchesSize) { - [nodesSizesChanged addObject:node]; - } - } -} - -- (void)relayoutAllNodesWithInvalidationBlock:(nullable void (^)())invalidationBlock -{ - ASDisplayNodeAssertMainThread(); - if (!_initialReloadDataHasBeenCalled) { - return; - } - - // Can't relayout right away because _visibleMap may not be up-to-date, - // i.e there might be some nodes that were measured using the old constrained size but haven't been added to _visibleMap - LOG(@"Edit Command - relayoutRows"); - [self _scheduleBlockOnMainSerialQueue:^{ - // Because -invalidateLayout doesn't trigger any operations by itself, and we answer queries from UICollectionView using layoutThatFits:, - // we invalidate the layout before we have updated all of the cells. Any cells that the collection needs the size of immediately will get - // -layoutThatFits: with a new constraint, on the main thread, and synchronously calculate them. Meanwhile, relayoutAllNodes will update - // the layout of any remaining nodes on background threads (and fast-return for any nodes that the UICV got to first). - if (invalidationBlock) { - invalidationBlock(); - } - [self _relayoutAllNodes]; - }]; -} - -- (void)_relayoutAllNodes -{ - ASDisplayNodeAssertMainThread(); - // Aggressively repopulate all supplemtary elements - // Assuming this method is run on the main serial queue, _pending and _visible maps are synced and can be manipulated directly. - ASDisplayNodeAssert(_visibleMap == _pendingMap, @"Expected visible and pending maps to be synchronized: %@", self); - - ASMutableElementMap *newMap = [_pendingMap mutableCopy]; - [self _updateSupplementaryNodesIntoMap:newMap - traitCollection:[self.node primitiveTraitCollection] - shouldFetchSizeRanges:YES - previousMap:_pendingMap]; - _pendingMap = [newMap copy]; - _visibleMap = _pendingMap; - - for (ASCollectionElement *element in _visibleMap) { - // Ignore this element if it is no longer in the latest data. It is still recognized in the UIKit world but will be deleted soon. - NSIndexPath *indexPathInPendingMap = [_pendingMap indexPathForElement:element]; - if (indexPathInPendingMap == nil) { - continue; - } - - NSString *kind = element.supplementaryElementKind ?: ASDataControllerRowNodeKind; - ASSizeRange newConstrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPathInPendingMap]; - - if (ASSizeRangeHasSignificantArea(newConstrainedSize)) { - element.constrainedSize = newConstrainedSize; - - // Node may not be allocated yet (e.g node virtualization or same size optimization) - // Call context.nodeIfAllocated here to avoid premature node allocation and layout - ASCellNode *node = element.nodeIfAllocated; - if (node) { - [self _layoutNode:node withConstrainedSize:newConstrainedSize]; - } - } - } -} - -# pragma mark - ASPrimitiveTraitCollection - -- (void)environmentDidChange -{ - ASPerformBlockOnMainThread(^{ - if (!_initialReloadDataHasBeenCalled) { - return; - } - - // Can't update the trait collection right away because _visibleMap may not be up-to-date, - // i.e there might be some elements that were allocated using the old trait collection but haven't been added to _visibleMap - [self _scheduleBlockOnMainSerialQueue:^{ - ASPrimitiveTraitCollection newTraitCollection = [self.node primitiveTraitCollection]; - for (ASCollectionElement *element in _visibleMap) { - element.traitCollection = newTraitCollection; - } - }]; - }); -} - -- (void)clearData -{ - ASDisplayNodeAssertMainThread(); - if (_initialReloadDataHasBeenCalled) { - [self waitUntilAllUpdatesAreProcessed]; - self.visibleMap = self.pendingMap = [[ASElementMap alloc] init]; - } -} - -# pragma mark - Helper methods - -- (void)_scheduleBlockOnMainSerialQueue:(dispatch_block_t)block -{ - ASDisplayNodeAssertMainThread(); - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - [_mainSerialQueue performBlockOnMainThread:block]; -} - -@end - -#endif \ No newline at end of file diff --git a/submodules/AsyncDisplayKit/Source/Details/ASDelegateProxy.h b/submodules/AsyncDisplayKit/Source/Details/ASDelegateProxy.h deleted file mode 100644 index 7ed406b6db..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASDelegateProxy.h +++ /dev/null @@ -1,56 +0,0 @@ -// -// ASDelegateProxy.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@class ASDelegateProxy; -@protocol ASDelegateProxyInterceptor -@required -// Called if the target object is discovered to be nil if it had been non-nil at init time. -// This happens if the object is deallocated, because the proxy must maintain a weak reference to avoid cycles. -// Though the target object may become nil, the interceptor must not; it is assumed the interceptor owns the proxy. -- (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy; -@end - -/** - * Stand-in for delegates like UITableView or UICollectionView's delegate / dataSource. - * Any selectors flagged by "interceptsSelector" are routed to the interceptor object and are not delivered to the target. - * Everything else leaves AsyncDisplayKit safely and arrives at the original target object. - */ - -@interface ASDelegateProxy : NSProxy - -- (instancetype)initWithTarget:(id)target interceptor:(id )interceptor; - -// This method must be overridden by a subclass. -- (BOOL)interceptsSelector:(SEL)selector; - -@end - -/** - * ASTableView intercepts and/or overrides a few of UITableView's critical data source and delegate methods. - * - * Any selector included in this function *MUST* be implemented by ASTableView. - */ - -@interface ASTableViewProxy : ASDelegateProxy -@end - -/** - * ASCollectionView intercepts and/or overrides a few of UICollectionView's critical data source and delegate methods. - * - * Any selector included in this function *MUST* be implemented by ASCollectionView. - */ - -@interface ASCollectionViewProxy : ASDelegateProxy -@end - -@interface ASPagerNodeProxy : ASDelegateProxy -@end - diff --git a/submodules/AsyncDisplayKit/Source/Details/ASDelegateProxy.mm b/submodules/AsyncDisplayKit/Source/Details/ASDelegateProxy.mm deleted file mode 100644 index a5368024d1..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASDelegateProxy.mm +++ /dev/null @@ -1,273 +0,0 @@ -// -// ASDelegateProxy.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import -#import -#import - -// UIKit performs a class check for UIDataSourceModelAssociation protocol conformance rather than an instance check, so -// the implementation of conformsToProtocol: below never gets called. We need to declare the two as conforming to the protocol here, then -// we need to implement dummy methods to get rid of a compiler warning about not conforming to the protocol. -@interface ASTableViewProxy () -@end - -@interface ASCollectionViewProxy () -@end - -@interface ASDelegateProxy (UIDataSourceModelAssociationPrivate) -- (nullable NSString *)_modelIdentifierForElementAtIndexPath:(NSIndexPath *)indexPath inView:(UIView *)view; -- (nullable NSIndexPath *)_indexPathForElementWithModelIdentifier:(NSString *)identifier inView:(UIView *)view; -@end - -@implementation ASTableViewProxy - -- (BOOL)interceptsSelector:(SEL)selector -{ - return ( - // handled by ASTableView node<->cell machinery - selector == @selector(tableView:cellForRowAtIndexPath:) || - selector == @selector(tableView:heightForRowAtIndexPath:) || - - // Selection, highlighting, menu - selector == @selector(tableView:willSelectRowAtIndexPath:) || - selector == @selector(tableView:didSelectRowAtIndexPath:) || - selector == @selector(tableView:willDeselectRowAtIndexPath:) || - selector == @selector(tableView:didDeselectRowAtIndexPath:) || - selector == @selector(tableView:shouldHighlightRowAtIndexPath:) || - selector == @selector(tableView:didHighlightRowAtIndexPath:) || - selector == @selector(tableView:didUnhighlightRowAtIndexPath:) || - selector == @selector(tableView:shouldShowMenuForRowAtIndexPath:) || - selector == @selector(tableView:canPerformAction:forRowAtIndexPath:withSender:) || - selector == @selector(tableView:performAction:forRowAtIndexPath:withSender:) || - - // handled by ASRangeController - selector == @selector(numberOfSectionsInTableView:) || - selector == @selector(tableView:numberOfRowsInSection:) || - - // reordering support - selector == @selector(tableView:canMoveRowAtIndexPath:) || - selector == @selector(tableView:moveRowAtIndexPath:toIndexPath:) || - - // used for ASCellNode visibility - selector == @selector(scrollViewDidScroll:) || - - // used for ASCellNode user interaction - selector == @selector(scrollViewWillBeginDragging:) || - selector == @selector(scrollViewDidEndDragging:willDecelerate:) || - - // used for ASRangeController visibility updates - selector == @selector(tableView:willDisplayCell:forRowAtIndexPath:) || - selector == @selector(tableView:didEndDisplayingCell:forRowAtIndexPath:) || - - // used for batch fetching API - selector == @selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:) || - selector == @selector(scrollViewDidEndDecelerating:) || - - // UIDataSourceModelAssociation - selector == @selector(modelIdentifierForElementAtIndexPath:inView:) || - selector == @selector(indexPathForElementWithModelIdentifier:inView:) - ); -} - -- (nullable NSString *)modelIdentifierForElementAtIndexPath:(NSIndexPath *)indexPath inView:(UIView *)view { - return [self _modelIdentifierForElementAtIndexPath:indexPath inView:view]; -} - -- (nullable NSIndexPath *)indexPathForElementWithModelIdentifier:(NSString *)identifier inView:(UIView *)view { - return [self _indexPathForElementWithModelIdentifier:identifier inView:view]; -} - -@end - -@implementation ASCollectionViewProxy - -- (BOOL)interceptsSelector:(SEL)selector -{ - return ( - // handled by ASCollectionView node<->cell machinery - selector == @selector(collectionView:cellForItemAtIndexPath:) || - selector == @selector(collectionView:layout:sizeForItemAtIndexPath:) || - selector == @selector(collectionView:layout:insetForSectionAtIndex:) || - selector == @selector(collectionView:layout:minimumLineSpacingForSectionAtIndex:) || - selector == @selector(collectionView:layout:minimumInteritemSpacingForSectionAtIndex:) || - selector == @selector(collectionView:layout:referenceSizeForHeaderInSection:) || - selector == @selector(collectionView:layout:referenceSizeForFooterInSection:) || - selector == @selector(collectionView:viewForSupplementaryElementOfKind:atIndexPath:) || - - // Selection, highlighting, menu - selector == @selector(collectionView:shouldSelectItemAtIndexPath:) || - selector == @selector(collectionView:didSelectItemAtIndexPath:) || - selector == @selector(collectionView:shouldDeselectItemAtIndexPath:) || - selector == @selector(collectionView:didDeselectItemAtIndexPath:) || - selector == @selector(collectionView:shouldHighlightItemAtIndexPath:) || - selector == @selector(collectionView:didHighlightItemAtIndexPath:) || - selector == @selector(collectionView:didUnhighlightItemAtIndexPath:) || - selector == @selector(collectionView:shouldShowMenuForItemAtIndexPath:) || - selector == @selector(collectionView:canPerformAction:forItemAtIndexPath:withSender:) || - selector == @selector(collectionView:performAction:forItemAtIndexPath:withSender:) || - - // Item counts - selector == @selector(numberOfSectionsInCollectionView:) || - selector == @selector(collectionView:numberOfItemsInSection:) || - - // Element appearance callbacks - selector == @selector(collectionView:willDisplayCell:forItemAtIndexPath:) || - selector == @selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:) || - selector == @selector(collectionView:willDisplaySupplementaryView:forElementKind:atIndexPath:) || - selector == @selector(collectionView:didEndDisplayingSupplementaryView:forElementOfKind:atIndexPath:) || - - // used for batch fetching API - selector == @selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:) || - selector == @selector(scrollViewDidEndDecelerating:) || - - // used for ASCellNode visibility - selector == @selector(scrollViewDidScroll:) || - - // used for ASCellNode user interaction - selector == @selector(scrollViewWillBeginDragging:) || - selector == @selector(scrollViewDidEndDragging:willDecelerate:) || - - // intercepted due to not being supported by ASCollectionView (prevent bugs caused by usage) - selector == @selector(collectionView:canMoveItemAtIndexPath:) || - selector == @selector(collectionView:moveItemAtIndexPath:toIndexPath:) || - - // UIDataSourceModelAssociation - selector == @selector(modelIdentifierForElementAtIndexPath:inView:) || - selector == @selector(indexPathForElementWithModelIdentifier:inView:) - ); -} - -- (nullable NSString *)modelIdentifierForElementAtIndexPath:(NSIndexPath *)indexPath inView:(UIView *)view { - return [self _modelIdentifierForElementAtIndexPath:indexPath inView:view]; -} - -- (nullable NSIndexPath *)indexPathForElementWithModelIdentifier:(NSString *)identifier inView:(UIView *)view { - return [self _indexPathForElementWithModelIdentifier:identifier inView:view]; -} - -@end - -@implementation ASPagerNodeProxy - -- (BOOL)interceptsSelector:(SEL)selector -{ - return ( - // handled by ASPagerDataSource node<->cell machinery - selector == @selector(collectionNode:nodeForItemAtIndexPath:) || - selector == @selector(collectionNode:nodeBlockForItemAtIndexPath:) || - selector == @selector(collectionNode:numberOfItemsInSection:) || - selector == @selector(collectionNode:constrainedSizeForItemAtIndexPath:) - ); -} - -@end - -@implementation ASDelegateProxy { - id __weak _interceptor; - id __weak _target; -} - -- (instancetype)initWithTarget:(id)target interceptor:(id )interceptor -{ - ASDisplayNodeAssert(interceptor, @"interceptor must not be nil"); - - _target = target ? : [NSNull null]; - _interceptor = interceptor; - - return self; -} - -- (BOOL)conformsToProtocol:(Protocol *)aProtocol -{ - id target = _target; - if (target) { - return [target conformsToProtocol:aProtocol]; - } else { - return [super conformsToProtocol:aProtocol]; - } -} - -- (BOOL)respondsToSelector:(SEL)aSelector -{ - if ([self interceptsSelector:aSelector]) { - return [_interceptor respondsToSelector:aSelector]; - } else { - // Also return NO if _target has become nil due to zeroing weak reference (or placeholder initialization). - return [_target respondsToSelector:aSelector]; - } -} - -- (id)forwardingTargetForSelector:(SEL)aSelector -{ - if ([self interceptsSelector:aSelector]) { - return _interceptor; - } else { - id target = _target; - if (target) { - return [target respondsToSelector:aSelector] ? target : nil; - } else { - // The _interceptor needs to be nilled out in this scenario. For that a strong reference needs to be created - // to be able to nil out the _interceptor but still let it know that the proxy target has deallocated - // We have to hold a strong reference to the interceptor as we have to nil it out and call the proxyTargetHasDeallocated - // The reason that the interceptor needs to be nilled out is that there maybe a change of a infinite loop, for example - // if a method will be called in the proxyTargetHasDeallocated: that again would trigger a whole new forwarding cycle - id interceptor = _interceptor; - _interceptor = nil; - [interceptor proxyTargetHasDeallocated:self]; - - return nil; - } - } -} - -- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector -{ - // Check for a compiled definition for the selector - NSMethodSignature *methodSignature = nil; - if ([self interceptsSelector:aSelector]) { - methodSignature = [[_interceptor class] instanceMethodSignatureForSelector:aSelector]; - } else { - methodSignature = [[_target class] instanceMethodSignatureForSelector:aSelector]; - } - - // Unfortunately, in order to get this object to work properly, the use of a method which creates an NSMethodSignature - // from a C string. -methodSignatureForSelector is called when a compiled definition for the selector cannot be found. - // This is the place where we have to create our own dud NSMethodSignature. This is necessary because if this method - // returns nil, a selector not found exception is raised. The string argument to -signatureWithObjCTypes: outlines - // the return type and arguments to the message. To return a dud NSMethodSignature, pretty much any signature will - // suffice. Since the -forwardInvocation call will do nothing if the delegate does not respond to the selector, - // the dud NSMethodSignature simply gets us around the exception. - return methodSignature ?: [NSMethodSignature signatureWithObjCTypes:"@^v^c"]; -} - -- (void)forwardInvocation:(NSInvocation *)invocation -{ - // If we are down here this means _interceptor and _target where nil. Just don't do anything to prevent a crash -} - -- (BOOL)interceptsSelector:(SEL)selector -{ - ASDisplayNodeAssert(NO, @"This method must be overridden by subclasses."); - return NO; -} - -- (nullable NSString *)_modelIdentifierForElementAtIndexPath:(NSIndexPath *)indexPath inView:(UIView *)view { - return [(id)_interceptor modelIdentifierForElementAtIndexPath:indexPath inView:view]; -} - -- (nullable NSIndexPath *)_indexPathForElementWithModelIdentifier:(NSString *)identifier inView:(UIView *)view { - return [(id)_interceptor indexPathForElementWithModelIdentifier:identifier inView:view]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASElementMap.h b/submodules/AsyncDisplayKit/Source/Details/ASElementMap.h deleted file mode 100644 index 1ea0ef58d4..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASElementMap.h +++ /dev/null @@ -1,137 +0,0 @@ -// -// ASElementMap.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@class ASCollectionElement, ASSection, UICollectionViewLayoutAttributes; -@protocol ASSectionContext; - -/** - * An immutable representation of the state of a collection view's data. - * All items and supplementary elements are represented by ASCollectionElement. - * Fast enumeration is in terms of ASCollectionElement. - */ -AS_SUBCLASSING_RESTRICTED -@interface ASElementMap : NSObject - -/** - * The total number of elements in this map. - */ -@property (readonly) NSUInteger count; - -/** - * The number of sections (of items) in this map. - */ -@property (readonly) NSInteger numberOfSections; - -/** - * The kinds of supplementary elements present in this map. O(1) - */ -@property (copy, readonly) NSArray *supplementaryElementKinds; - -/** - * Returns number of items in the given section. O(1) - */ -- (NSInteger)numberOfItemsInSection:(NSInteger)section; - -/** - * Returns the context object for the given section, if any. O(1) - */ -- (nullable id)contextForSection:(NSInteger)section; - -/** - * All the index paths for all the items in this map. O(N) - * - * This property may be removed in the future, since it doesn't account for supplementary nodes. - */ -@property (copy, readonly) NSArray *itemIndexPaths; - -/** - * All the item elements in this map, in ascending order. O(N) - */ -@property (copy, readonly) NSArray *itemElements; - -/** - * Returns the index path that corresponds to the same element in @c map at the given @c indexPath. - * O(1) for items, fast O(N) for sections. - * - * Note you can pass "section index paths" of length 1 and get a corresponding section index path. - */ -- (nullable NSIndexPath *)convertIndexPath:(NSIndexPath *)indexPath fromMap:(ASElementMap *)map; - -/** - * Returns the section index into the receiver that corresponds to the same element in @c map at @c sectionIndex. Fast O(N). - * - * Returns @c NSNotFound if the section does not exist in the receiver. - */ -- (NSInteger)convertSection:(NSInteger)sectionIndex fromMap:(ASElementMap *)map; - -/** - * Returns the index path for the given element. O(1) - */ -- (nullable NSIndexPath *)indexPathForElement:(ASCollectionElement *)element; - -/** - * Returns the index path for the given element, if it represents a cell. O(1) - */ -- (nullable NSIndexPath *)indexPathForElementIfCell:(ASCollectionElement *)element; - -/** - * Returns the item-element at the given index path. O(1) - */ -- (nullable ASCollectionElement *)elementForItemAtIndexPath:(NSIndexPath *)indexPath; - -/** - * Returns the element for the supplementary element of the given kind at the given index path. O(1) - */ -- (nullable ASCollectionElement *)supplementaryElementOfKind:(NSString *)supplementaryElementKind atIndexPath:(NSIndexPath *)indexPath; - -/** - * Returns the element that corresponds to the given layout attributes, if any. - * - * NOTE: This method only regards the category, kind, and index path of the attributes object. Elements do not - * have any concept of size/position. - */ -- (nullable ASCollectionElement *)elementForLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes; - -/** - * A very terse description e.g. { itemCounts = [ ] } - */ -@property (readonly) NSString *smallDescription; - -#pragma mark - Initialization -- Only Useful to ASDataController - - -// SectionIndex -> ItemIndex -> Element -typedef NSArray *> ASCollectionElementTwoDimensionalArray; - -// ElementKind -> IndexPath -> Element -typedef NSDictionary *> ASSupplementaryElementDictionary; - -/** - * Create a new element map for this dataset. You probably don't need to use this – ASDataController is the only one who creates these. - * - * @param sections The array of ASSection objects. - * @param items A 2D array of ASCollectionElements, for each item. - * @param supplementaryElements A dictionary of gathered supplementary elements. - */ -- (instancetype)initWithSections:(NSArray *)sections - items:(ASCollectionElementTwoDimensionalArray *)items - supplementaryElements:(ASSupplementaryElementDictionary *)supplementaryElements; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASElementMap.mm b/submodules/AsyncDisplayKit/Source/Details/ASElementMap.mm deleted file mode 100644 index 6df89ff6cd..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASElementMap.mm +++ /dev/null @@ -1,280 +0,0 @@ -// -// ASElementMap.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import -#import -#import -#import -#import -#import -#import - -@interface ASElementMap () - -@property (nonatomic, readonly) NSArray *sections; - -// Element -> IndexPath -@property (nonatomic, readonly) NSMapTable *elementToIndexPathMap; - -// The items, in a 2D array -@property (nonatomic, readonly) ASCollectionElementTwoDimensionalArray *sectionsOfItems; - -@property (nonatomic, readonly) ASSupplementaryElementDictionary *supplementaryElements; - -@end - -@implementation ASElementMap - -- (instancetype)init -{ - return [self initWithSections:@[] items:@[] supplementaryElements:@{}]; -} - -- (instancetype)initWithSections:(NSArray *)sections items:(ASCollectionElementTwoDimensionalArray *)items supplementaryElements:(ASSupplementaryElementDictionary *)supplementaryElements -{ - NSCParameterAssert(items.count == sections.count); - - if (self = [super init]) { - _sections = [sections copy]; - _sectionsOfItems = [[NSArray alloc] initWithArray:items copyItems:YES]; - _supplementaryElements = [[NSDictionary alloc] initWithDictionary:supplementaryElements copyItems:YES]; - - // Setup our index path map - _elementToIndexPathMap = [NSMapTable mapTableWithKeyOptions:(NSMapTableStrongMemory | NSMapTableObjectPointerPersonality) valueOptions:NSMapTableCopyIn]; - NSInteger s = 0; - for (NSArray *section in _sectionsOfItems) { - NSInteger i = 0; - for (ASCollectionElement *element in section) { - NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:s]; - [_elementToIndexPathMap setObject:indexPath forKey:element]; - i++; - } - s++; - } - for (NSDictionary *supplementariesForKind in [_supplementaryElements objectEnumerator]) { - [supplementariesForKind enumerateKeysAndObjectsUsingBlock:^(NSIndexPath *_Nonnull indexPath, ASCollectionElement * _Nonnull element, BOOL * _Nonnull stop) { - [_elementToIndexPathMap setObject:indexPath forKey:element]; - }]; - } - } - return self; -} - -- (NSUInteger)count -{ - return _elementToIndexPathMap.count; -} - -- (NSArray *)itemIndexPaths -{ - return ASIndexPathsForTwoDimensionalArray(_sectionsOfItems); -} - -- (NSArray *)itemElements -{ - return ASElementsInTwoDimensionalArray(_sectionsOfItems); -} - -- (NSInteger)numberOfSections -{ - return _sectionsOfItems.count; -} - -- (NSArray *)supplementaryElementKinds -{ - return _supplementaryElements.allKeys; -} - -- (NSInteger)numberOfItemsInSection:(NSInteger)section -{ - if (![self sectionIndexIsValid:section assert:YES]) { - return 0; - } - - return _sectionsOfItems[section].count; -} - -- (id)contextForSection:(NSInteger)section -{ - if (![self sectionIndexIsValid:section assert:NO]) { - return nil; - } - - return _sections[section].context; -} - -- (nullable NSIndexPath *)indexPathForElement:(ASCollectionElement *)element -{ - return element ? [_elementToIndexPathMap objectForKey:element] : nil; -} - -- (nullable NSIndexPath *)indexPathForElementIfCell:(ASCollectionElement *)element -{ - if (element.supplementaryElementKind == nil) { - return [self indexPathForElement:element]; - } else { - return nil; - } -} - -- (nullable ASCollectionElement *)elementForItemAtIndexPath:(NSIndexPath *)indexPath -{ - NSInteger section, item; - if (![self itemIndexPathIsValid:indexPath assert:NO item:&item section:§ion]) { - return nil; - } - - return _sectionsOfItems[section][item]; -} - -- (nullable ASCollectionElement *)supplementaryElementOfKind:(NSString *)supplementaryElementKind atIndexPath:(NSIndexPath *)indexPath -{ - return _supplementaryElements[supplementaryElementKind][indexPath]; -} - -- (ASCollectionElement *)elementForLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes -{ - switch (layoutAttributes.representedElementCategory) { - case UICollectionElementCategoryCell: - // Cell - return [self elementForItemAtIndexPath:layoutAttributes.indexPath]; - case UICollectionElementCategorySupplementaryView: - // Supplementary element. - return [self supplementaryElementOfKind:layoutAttributes.representedElementKind atIndexPath:layoutAttributes.indexPath]; - case UICollectionElementCategoryDecorationView: - // No support for decoration views. - return nil; - } -} - -- (NSIndexPath *)convertIndexPath:(NSIndexPath *)indexPath fromMap:(ASElementMap *)map -{ - if (indexPath.item == NSNotFound) { - // Section index path - NSInteger result = [self convertSection:indexPath.section fromMap:map]; - return (result != NSNotFound ? [NSIndexPath indexPathWithIndex:result] : nil); - } else { - // Item index path - ASCollectionElement *element = [map elementForItemAtIndexPath:indexPath]; - return [self indexPathForElement:element]; - } -} - -- (NSInteger)convertSection:(NSInteger)sectionIndex fromMap:(ASElementMap *)map -{ - if (![map sectionIndexIsValid:sectionIndex assert:YES]) { - return NSNotFound; - } - - ASSection *section = map.sections[sectionIndex]; - return [_sections indexOfObjectIdenticalTo:section]; -} - -#pragma mark - NSCopying - -- (id)copyWithZone:(NSZone *)zone -{ - return self; -} - -// NSMutableCopying conformance is declared in ASMutableElementMap.h, so that most consumers of ASElementMap don't bother with it. -#pragma mark - NSMutableCopying - -- (id)mutableCopyWithZone:(NSZone *)zone -{ - return [[ASMutableElementMap alloc] initWithSections:_sections items:_sectionsOfItems supplementaryElements:_supplementaryElements]; -} - -#pragma mark - NSFastEnumeration - -- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id _Nullable __unsafe_unretained [])buffer count:(NSUInteger)len -{ - return [_elementToIndexPathMap countByEnumeratingWithState:state objects:buffer count:len]; -} - -- (NSString *)smallDescription -{ - NSMutableArray *sectionDescriptions = [NSMutableArray array]; - - NSUInteger i = 0; - for (NSArray *section in _sectionsOfItems) { - [sectionDescriptions addObject:[NSString stringWithFormat:@"", i, section.count]]; - i++; - } - return ASObjectDescriptionMakeWithoutObject(@[ @{ @"itemCounts": sectionDescriptions }]); -} - -#pragma mark - ASDescriptionProvider - -- (NSString *)description -{ - return ASObjectDescriptionMake(self, [self propertiesForDescription]); -} - -- (NSMutableArray *)propertiesForDescription -{ - NSMutableArray *result = [NSMutableArray array]; - [result addObject:@{ @"items" : _sectionsOfItems }]; - [result addObject:@{ @"supplementaryElements" : _supplementaryElements }]; - return result; -} - -#pragma mark - Internal - -/** - * Fails assert + return NO if section is out of bounds. - */ -- (BOOL)sectionIndexIsValid:(NSInteger)section assert:(BOOL)assert -{ - NSInteger sectionCount = _sectionsOfItems.count; - if (section >= sectionCount || section < 0) { - if (assert) { - ASDisplayNodeFailAssert(@"Invalid section index %ld when there are only %ld sections!", (long)section, (long)sectionCount); - } - return NO; - } else { - return YES; - } -} - -/** - * If indexPath is nil, just returns NO. - * If indexPath is invalid, fails assertion and returns NO. - * Otherwise returns YES and sets the item & section. - */ -- (BOOL)itemIndexPathIsValid:(NSIndexPath *)indexPath assert:(BOOL)assert item:(out NSInteger *)outItem section:(out NSInteger *)outSection -{ - if (indexPath == nil) { - return NO; - } - - NSInteger section = indexPath.section; - if (![self sectionIndexIsValid:section assert:assert]) { - return NO; - } - - NSInteger itemCount = _sectionsOfItems[section].count; - NSInteger item = indexPath.item; - if (item >= itemCount || item < 0) { - if (assert) { - ASDisplayNodeFailAssert(@"Invalid item index %ld in section %ld which only has %ld items!", (long)item, (long)section, (long)itemCount); - } - return NO; - } - *outItem = item; - *outSection = section; - return YES; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASEventLog.h b/submodules/AsyncDisplayKit/Source/Details/ASEventLog.h deleted file mode 100644 index 9a147f259d..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASEventLog.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// ASEventLog.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -#ifndef ASEVENTLOG_CAPACITY -#define ASEVENTLOG_CAPACITY 5 -#endif - -#ifndef ASEVENTLOG_ENABLE -#define ASEVENTLOG_ENABLE 0 -#endif - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED -@interface ASEventLog : NSObject - -/** - * Create a new event log. - * - * @param anObject The object whose events we are logging. This object is not retained. - */ -- (instancetype)initWithObject:(id)anObject; - -- (void)logEventWithBacktrace:(nullable NSArray *)backtrace format:(NSString *)format, ... NS_FORMAT_FUNCTION(2, 3); - -- (instancetype)init NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Details/ASEventLog.mm b/submodules/AsyncDisplayKit/Source/Details/ASEventLog.mm deleted file mode 100644 index 25ecc29968..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASEventLog.mm +++ /dev/null @@ -1,121 +0,0 @@ -// -// ASEventLog.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import -#import - -@implementation ASEventLog { - AS::RecursiveMutex __instanceLock__; - - // The index of the most recent log entry. -1 until first entry. - NSInteger _eventLogHead; - - // A description of the object we're logging for. This is immutable. - NSString *_objectDescription; -} - -/** - * Even just when debugging, all these events can take up considerable memory. - * Store them in a shared NSCache to limit the total consumption. - */ -+ (NSCache *> *)contentsCache -{ - static NSCache *cache; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - cache = [[NSCache alloc] init]; - }); - return cache; -} - -- (instancetype)initWithObject:(id)anObject -{ - if ((self = [super init])) { - _objectDescription = ASObjectDescriptionMakeTiny(anObject); - _eventLogHead = -1; - } - return self; -} - -- (instancetype)init -{ - // This method is marked unavailable so the compiler won't let them call it. - ASDisplayNodeFailAssert(@"Failed to call initWithObject:"); - return nil; -} - -- (void)logEventWithBacktrace:(NSArray *)backtrace format:(NSString *)format, ... -{ - va_list args; - va_start(args, format); - ASTraceEvent *event = [[ASTraceEvent alloc] initWithBacktrace:backtrace - format:format - arguments:args]; - va_end(args); - - AS::MutexLocker l(__instanceLock__); - NSCache *cache = [ASEventLog contentsCache]; - NSMutableArray *events = [cache objectForKey:self]; - if (events == nil) { - events = [NSMutableArray arrayWithObject:event]; - [cache setObject:events forKey:self]; - _eventLogHead = 0; - return; - } - - // Increment the head index. - _eventLogHead = (_eventLogHead + 1) % ASEVENTLOG_CAPACITY; - if (_eventLogHead < events.count) { - [events replaceObjectAtIndex:_eventLogHead withObject:event]; - } else { - [events insertObject:event atIndex:_eventLogHead]; - } -} - -- (NSArray *)events -{ - NSMutableArray *events = [[ASEventLog contentsCache] objectForKey:self]; - if (events == nil) { - return nil; - } - - AS::MutexLocker l(__instanceLock__); - NSUInteger tail = (_eventLogHead + 1); - NSUInteger count = events.count; - - NSMutableArray *result = [NSMutableArray array]; - - // Start from `tail` and go through array, wrapping around when we exceed end index. - for (NSUInteger actualIndex = 0; actualIndex < ASEVENTLOG_CAPACITY; actualIndex++) { - NSInteger ringIndex = (tail + actualIndex) % ASEVENTLOG_CAPACITY; - if (ringIndex < count) { - [result addObject:events[ringIndex]]; - } - } - return result; -} - -- (NSString *)description -{ - /** - * This description intentionally doesn't follow the standard description format. - * Since this is a log, it's important for the description to look a certain way, and - * the formal description style doesn't allow for newlines and has a ton of punctuation. - */ - NSArray *events = [self events]; - if (events == nil) { - return [NSString stringWithFormat:@"Event log for %@ was purged to conserve memory.", _objectDescription]; - } else { - return [NSString stringWithFormat:@"Event log for %@. Events: %@", _objectDescription, events]; - } -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/Details/ASHighlightOverlayLayer.h b/submodules/AsyncDisplayKit/Source/Details/ASHighlightOverlayLayer.h deleted file mode 100644 index aff6694bf1..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASHighlightOverlayLayer.h +++ /dev/null @@ -1,51 +0,0 @@ -// -// ASHighlightOverlayLayer.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED -@interface ASHighlightOverlayLayer : CALayer - -/** - @summary Initializes with CGRects for the highlighting, in the targetLayer's coordinate space. - - @desc This is the designated initializer. - - @param rects Array containing CGRects wrapped in NSValue. - @param targetLayer The layer that the rects are relative to. The rects will be translated to the receiver's coordinate space when rendering. - */ -- (instancetype)initWithRects:(NSArray *)rects targetLayer:(nullable CALayer *)targetLayer; - -/** - @summary Initializes with CGRects for the highlighting, in the receiver's coordinate space. - - @param rects Array containing CGRects wrapped in NSValue. - */ -- (instancetype)initWithRects:(NSArray *)rects; - -@property (nullable, nonatomic) __attribute__((NSObject)) CGColorRef highlightColor; -@property (nonatomic, weak) CALayer *targetLayer; - -@end - -@interface CALayer (ASHighlightOverlayLayerSupport) - -/** - @summary Set to YES to indicate to a sublayer that this is where highlight overlay layers (for pressed states) should - be added so that the highlight won't be clipped by a neighboring layer. - */ -@property (nonatomic, setter=as_setAllowsHighlightDrawing:) BOOL as_allowsHighlightDrawing; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Details/ASHighlightOverlayLayer.mm b/submodules/AsyncDisplayKit/Source/Details/ASHighlightOverlayLayer.mm deleted file mode 100644 index 03cf468165..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASHighlightOverlayLayer.mm +++ /dev/null @@ -1,134 +0,0 @@ -// -// ASHighlightOverlayLayer.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import - -#import - -static const CGFloat kCornerRadius = 2.5; -static const UIEdgeInsets padding = {2, 4, 1.5, 4}; - -@implementation ASHighlightOverlayLayer -{ - NSArray *_rects; -} - -+ (id)defaultValueForKey:(NSString *)key -{ - if ([key isEqualToString:@"contentsScale"]) { - return @(ASScreenScale()); - } else if ([key isEqualToString:@"highlightColor"]) { - CGFloat components[] = {0, 0, 0, 0.25}; - CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); - CGColorRef color = CGColorCreate(colorSpace, components); - CGColorSpaceRelease(colorSpace); - return CFBridgingRelease(color); - } else { - return [super defaultValueForKey:key]; - } -} - -+ (BOOL)needsDisplayForKey:(NSString *)key -{ - if ([key isEqualToString:@"bounds"]) { - return YES; - } else { - return [super needsDisplayForKey:key]; - } -} - -+ (id)defaultActionForKey:(NSString *)event -{ - return (id)[NSNull null]; -} - -- (instancetype)initWithRects:(NSArray *)rects -{ - return [self initWithRects:rects targetLayer:nil]; -} - -- (instancetype)initWithRects:(NSArray *)rects targetLayer:(id)targetLayer -{ - if (self = [super init]) { - _rects = [rects copy]; - _targetLayer = targetLayer; - } - return self; -} - -@dynamic highlightColor; - -- (void)drawInContext:(CGContextRef)ctx -{ - [super drawInContext:ctx]; - - CGAffineTransform affine = CGAffineTransformIdentity; - CGMutablePathRef highlightPath = CGPathCreateMutable(); - CALayer *targetLayer = self.targetLayer; - - for (NSValue *value in _rects) { - CGRect rect = [value CGRectValue]; - - // Don't highlight empty rects. - if (CGRectIsEmpty(rect)) { - continue; - } - - if (targetLayer != nil) { - rect = [self convertRect:rect fromLayer:targetLayer]; - } - rect = CGRectMake(std::round(rect.origin.x), std::round(rect.origin.y), std::round(rect.size.width), std::round(rect.size.height)); - - CGFloat minX = rect.origin.x - padding.left; - CGFloat maxX = CGRectGetMaxX(rect) + padding.right; - CGFloat midX = (maxX - minX) / 2 + minX; - CGFloat minY = rect.origin.y - padding.top; - CGFloat maxY = CGRectGetMaxY(rect) + padding.bottom; - CGFloat midY = (maxY - minY) / 2 + minY; - - CGPathMoveToPoint(highlightPath, &affine, minX, midY); - CGPathAddArcToPoint(highlightPath, &affine, minX, maxY, midX, maxY, kCornerRadius); - CGPathAddArcToPoint(highlightPath, &affine, maxX, maxY, maxX, midY, kCornerRadius); - CGPathAddArcToPoint(highlightPath, &affine, maxX, minY, midX, minY, kCornerRadius); - CGPathAddArcToPoint(highlightPath, &affine, minX, minY, minX, midY, kCornerRadius); - CGPathCloseSubpath(highlightPath); - } - - CGContextAddPath(ctx, highlightPath); - CGContextSetFillColorWithColor(ctx, self.highlightColor); - CGContextDrawPath(ctx, kCGPathFill); - CGPathRelease(highlightPath); -} - -- (CALayer *)hitTest:(CGPoint)p -{ - // Don't handle taps - return nil; -} - -@end - -@implementation CALayer (ASHighlightOverlayLayerSupport) - -static NSString *kAllowsHighlightDrawingKey = @"allows_highlight_drawing"; - -- (BOOL)as_allowsHighlightDrawing -{ - return [[self valueForKey:kAllowsHighlightDrawingKey] boolValue]; -} - -- (void)as_setAllowsHighlightDrawing:(BOOL)allowsHighlightDrawing -{ - [self setValue:@(allowsHighlightDrawing) forKey:kAllowsHighlightDrawingKey]; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/Details/ASImageContainerProtocolCategories.h b/submodules/AsyncDisplayKit/Source/Details/ASImageContainerProtocolCategories.h deleted file mode 100644 index 44ddc354fd..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASImageContainerProtocolCategories.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// ASImageContainerProtocolCategories.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -@interface UIImage (ASImageContainerProtocol) - -@end - -@interface NSData (ASImageContainerProtocol) - -@end diff --git a/submodules/AsyncDisplayKit/Source/Details/ASImageContainerProtocolCategories.mm b/submodules/AsyncDisplayKit/Source/Details/ASImageContainerProtocolCategories.mm deleted file mode 100644 index c9316c32ab..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASImageContainerProtocolCategories.mm +++ /dev/null @@ -1,38 +0,0 @@ -// -// ASImageContainerProtocolCategories.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@implementation UIImage (ASImageContainerProtocol) - -- (UIImage *)asdk_image -{ - return self; -} - -- (NSData *)asdk_animatedImageData -{ - return nil; -} - -@end - -@implementation NSData (ASImageContainerProtocol) - -- (UIImage *)asdk_image -{ - return nil; -} - -- (NSData *)asdk_animatedImageData -{ - return self; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/Details/ASImageProtocols.h b/submodules/AsyncDisplayKit/Source/Details/ASImageProtocols.h deleted file mode 100644 index fae3b9f5b4..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASImageProtocols.h +++ /dev/null @@ -1,240 +0,0 @@ -// -// ASImageProtocols.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@protocol ASAnimatedImageProtocol; - -@protocol ASImageContainerProtocol - -- (nullable UIImage *)asdk_image; -- (nullable NSData *)asdk_animatedImageData; - -@end - -typedef void(^ASImageCacherCompletion)(id _Nullable imageFromCache); - -@protocol ASImageCacheProtocol - -/** - @abstract Attempts to fetch an image with the given URL from the cache. - @param URL The URL of the image to retrieve from the cache. - @param callbackQueue The queue to call `completion` on. - @param completion The block to be called when the cache has either hit or missed. - @discussion If `URL` is nil, `completion` will be invoked immediately with a nil image. This method should not block - the calling thread as it is likely to be called from the main thread. - */ -- (void)cachedImageWithURL:(NSURL *)URL - callbackQueue:(dispatch_queue_t)callbackQueue - completion:(ASImageCacherCompletion)completion; - -@optional - -/** - @abstract Attempts to fetch an image with the given URL from a memory cache. - @param URL The URL of the image to retrieve from the cache. - @discussion This method exists to support synchronous rendering of nodes. Before the layer is drawn, this method - is called to attempt to get the image out of the cache synchronously. This allows drawing to occur on the main thread - if displaysAsynchronously is set to NO or recursivelyEnsureDisplaySynchronously: has been called. - - This method *should* block the calling thread to fetch the image from a fast memory cache. It is OK to return nil from - this method and instead support only cachedImageWithURL:callbackQueue:completion: however, synchronous rendering will - not be possible. - */ -- (nullable id )synchronouslyFetchedCachedImageWithURL:(NSURL *)URL; - -/** - @abstract Called during clearPreloadedData. Allows the cache to optionally trim items. - @note Depending on your caches implementation you may *not* wish to respond to this method. It is however useful - if you have a memory and disk cache in which case you'll likely want to clear out the memory cache. - */ -- (void)clearFetchedImageFromCacheWithURL:(NSURL *)URL; - -@end - -/** - @param image The image that was downloaded, if the image could be successfully downloaded; nil otherwise. - @param error An error describing why the download of `URL` failed, if the download failed; nil otherwise. - @param downloadIdentifier The identifier for the download task that completed. - @param userInfo Any additional info that your downloader would like to communicate through Texture. - */ -typedef void(^ASImageDownloaderCompletion)(id _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier, id _Nullable userInfo); - -/** - @param progress The progress of the download, in the range of (0.0, 1.0), inclusive. - */ -typedef void(^ASImageDownloaderProgress)(CGFloat progress); -typedef void(^ASImageDownloaderProgressImage)(UIImage *progressImage, CGFloat progress, id _Nullable downloadIdentifier); - -typedef NS_ENUM(NSUInteger, ASImageDownloaderPriority) { - ASImageDownloaderPriorityPreload = 0, - ASImageDownloaderPriorityImminent, - ASImageDownloaderPriorityVisible -}; - -@protocol ASImageDownloaderProtocol - -@required - -/** - @abstract Downloads an image with the given URL. - @param URL The URL of the image to download. - @param callbackQueue The queue to call `downloadProgressBlock` and `completion` on. - @param downloadProgress The block to be invoked when the download of `URL` progresses. - @param completion The block to be invoked when the download has completed, or has failed. - @discussion This method is likely to be called on the main thread, so any custom implementations should make sure to background any expensive download operations. - @result An opaque identifier to be used in canceling the download, via `cancelImageDownloadForIdentifier:`. You must - retain the identifier if you wish to use it later. - */ -- (nullable id)downloadImageWithURL:(NSURL *)URL - callbackQueue:(dispatch_queue_t)callbackQueue - downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress - completion:(ASImageDownloaderCompletion)completion; - -/** - @abstract Cancels an image download. - @param downloadIdentifier The opaque download identifier object returned from - `downloadImageWithURL:callbackQueue:downloadProgress:completion:`. - @discussion This method has no effect if `downloadIdentifier` is nil. - */ -- (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier; - -@optional - -/** - @abstract Downloads an image with the given URL. - @param URL The URL of the image to download. - @param priority The priority at which the image should be downloaded. - @param callbackQueue The queue to call `downloadProgressBlock` and `completion` on. - @param downloadProgress The block to be invoked when the download of `URL` progresses. - @param completion The block to be invoked when the download has completed, or has failed. - @discussion This method is likely to be called on the main thread, so any custom implementations should make sure to background any expensive download operations. - @note If this method is implemented, it will be called instead of the required method (`downloadImageWithURL:callbackQueue:downloadProgress:completion:`). - @result An opaque identifier to be used in canceling the download, via `cancelImageDownloadForIdentifier:`. You must - retain the identifier if you wish to use it later. - */ -- (nullable id)downloadImageWithURL:(NSURL *)URL - priority:(ASImageDownloaderPriority)priority - callbackQueue:(dispatch_queue_t)callbackQueue - downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress - completion:(ASImageDownloaderCompletion)completion; - -/** - @abstract Cancels an image download, however indicating resume data should be stored in case of redownload. - @param downloadIdentifier The opaque download identifier object returned from - `downloadImageWithURL:callbackQueue:downloadProgress:completion:`. - @discussion This method has no effect if `downloadIdentifier` is nil. If implemented, this method - may be called instead of `cancelImageDownloadForIdentifier:` in cases where ASDK believes there's a chance - the image download will be resumed (currently when an image exits preload range). You can use this to store - any data that has already been downloaded for use in resuming the download later. - */ -- (void)cancelImageDownloadWithResumePossibilityForIdentifier:(id)downloadIdentifier; - -/** - @abstract Return an object that conforms to ASAnimatedImageProtocol - @param animatedImageData Data that represents an animated image. - */ -- (nullable id )animatedImageWithData:(NSData *)animatedImageData; - - -/** - @abstract Sets block to be called when a progress image is available. - @param progressBlock The block to be invoked when the download has a progressive render of an image available. - @param callbackQueue The queue to call `progressImageBlock` on. - @param downloadIdentifier The opaque download identifier object returned from - `downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:`. - */ -- (void)setProgressImageBlock:(nullable ASImageDownloaderProgressImage)progressBlock - callbackQueue:(dispatch_queue_t)callbackQueue - withDownloadIdentifier:(id)downloadIdentifier; - -/** - @abstract Called to indicate what priority an image should be downloaded at. - @param priority The priority at which the image should be downloaded. - @param downloadIdentifier The opaque download identifier object returned from - `downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:`. - */ -- (void)setPriority:(ASImageDownloaderPriority)priority -withDownloadIdentifier:(id)downloadIdentifier; - -@end - -@protocol ASAnimatedImageProtocol - -@optional - -/** - @abstract A block which receives the cover image. Should be called when the objects cover image is ready. - */ -@property (nonatomic) void (^coverImageReadyCallback)(UIImage *coverImage); - -/** - @abstract Returns whether the supplied data contains a supported animated image format. - @param data the data to check if contains a supported animated image. - */ -- (BOOL)isDataSupported:(NSData *)data; - - -@required - -/** - @abstract Return the objects's cover image. - */ -@property (nonatomic, readonly) UIImage *coverImage; -/** - @abstract Return a boolean to indicate that the cover image is ready. - */ -@property (nonatomic, readonly) BOOL coverImageReady; -/** - @abstract Return the total duration of the animated image's playback. - */ -@property (nonatomic, readonly) CFTimeInterval totalDuration; -/** - @abstract Return the interval at which playback should occur. Will be set to a CADisplayLink's frame interval. - */ -@property (nonatomic, readonly) NSUInteger frameInterval; -/** - @abstract Return the total number of loops the animated image should play or 0 to loop infinitely. - */ -@property (nonatomic, readonly) size_t loopCount; -/** - @abstract Return the total number of frames in the animated image. - */ -@property (nonatomic, readonly) size_t frameCount; -/** - @abstract Return YES when playback is ready to occur. - */ -@property (nonatomic, readonly) BOOL playbackReady; -/** - @abstract Return any error that has occured. Playback will be paused if this returns non-nil. - */ -@property (nonatomic, readonly) NSError *error; -/** - @abstract Should be called when playback is ready. - */ -@property (nonatomic) dispatch_block_t playbackReadyCallback; - -/** - @abstract Return the image at a given index. - */ -- (CGImageRef)imageAtIndex:(NSUInteger)index; -/** - @abstract Return the duration at a given index. - */ -- (CFTimeInterval)durationAtIndex:(NSUInteger)index; -/** - @abstract Clear any cached data. Called when playback is paused. - */ -- (void)clearAnimatedImageCache; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Details/ASIntegerMap.h b/submodules/AsyncDisplayKit/Source/Details/ASIntegerMap.h deleted file mode 100644 index ece6f5a8bf..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASIntegerMap.h +++ /dev/null @@ -1,68 +0,0 @@ -// -// ASIntegerMap.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * An objective-C wrapper for unordered_map. - */ -AS_SUBCLASSING_RESTRICTED -@interface ASIntegerMap : NSObject - -/** - * Creates a map based on the specified update to an array. - * - * If oldCount is 0, returns the empty map. - * If deleted and inserted are empty, returns the identity map. - */ -+ (ASIntegerMap *)mapForUpdateWithOldCount:(NSInteger)oldCount - deleted:(nullable NSIndexSet *)deleted - inserted:(nullable NSIndexSet *)inserted NS_RETURNS_RETAINED; - -/** - * A singleton that maps each integer to itself. Its inverse is itself. - * - * Note: You cannot mutate this. - */ -@property (class, readonly) ASIntegerMap *identityMap; -+ (ASIntegerMap *)identityMap NS_RETURNS_RETAINED; - -/** - * A singleton that returns NSNotFound for all keys. Its inverse is itself. - * - * Note: You cannot mutate this. - */ -@property (class, readonly) ASIntegerMap *emptyMap; -+ (ASIntegerMap *)emptyMap NS_RETURNS_RETAINED; - -/** - * Retrieves the integer for a given key, or NSNotFound if the key is not found. - * - * @param key A key to lookup the value for. - */ -- (NSInteger)integerForKey:(NSInteger)key; - -/** - * Sets the value for a given key. - * - * @param value The new value. - * @param key The key to store the value for. - */ -- (void)setInteger:(NSInteger)value forKey:(NSInteger)key; - -/** - * Create and return a map with the inverse mapping. - */ -- (ASIntegerMap *)inverseMap; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Details/ASIntegerMap.mm b/submodules/AsyncDisplayKit/Source/Details/ASIntegerMap.mm deleted file mode 100644 index b07a7a1806..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASIntegerMap.mm +++ /dev/null @@ -1,185 +0,0 @@ -// -// ASIntegerMap.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASIntegerMap.h" -#import -#import -#import -#import - -/** - * This is just a friendly Objective-C interface to unordered_map - */ -@interface ASIntegerMap () -@end - -@implementation ASIntegerMap { - std::unordered_map _map; - BOOL _isIdentity; - BOOL _isEmpty; - BOOL _immutable; // identity map and empty mape are immutable. -} - -#pragma mark - Singleton - -+ (ASIntegerMap *)identityMap NS_RETURNS_RETAINED -{ - static ASIntegerMap *identityMap; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - identityMap = [[ASIntegerMap alloc] init]; - identityMap->_isIdentity = YES; - identityMap->_immutable = YES; - }); - return identityMap; -} - -+ (ASIntegerMap *)emptyMap NS_RETURNS_RETAINED -{ - static ASIntegerMap *emptyMap; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - emptyMap = [[ASIntegerMap alloc] init]; - emptyMap->_isEmpty = YES; - emptyMap->_immutable = YES; - }); - return emptyMap; -} - -+ (ASIntegerMap *)mapForUpdateWithOldCount:(NSInteger)oldCount deleted:(NSIndexSet *)deletions inserted:(NSIndexSet *)insertions NS_RETURNS_RETAINED -{ - if (oldCount == 0) { - return ASIntegerMap.emptyMap; - } - - if (deletions.count == 0 && insertions.count == 0) { - return ASIntegerMap.identityMap; - } - - ASIntegerMap *result = [[ASIntegerMap alloc] init]; - // Start with the old indexes - NSMutableIndexSet *indexes = [NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(0, oldCount)]; - - // Descending order, shift deleted ranges left - [deletions enumerateRangesWithOptions:NSEnumerationReverse usingBlock:^(NSRange range, BOOL * _Nonnull stop) { - [indexes shiftIndexesStartingAtIndex:NSMaxRange(range) by:-range.length]; - }]; - - // Ascending order, shift inserted ranges right - [insertions enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { - [indexes shiftIndexesStartingAtIndex:range.location by:range.length]; - }]; - - __block NSInteger oldIndex = 0; - [indexes enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { - // Note we advance oldIndex unconditionally, not newIndex - for (NSInteger newIndex = range.location; newIndex < NSMaxRange(range); oldIndex++) { - if ([deletions containsIndex:oldIndex]) { - // index was deleted, do nothing, just let oldIndex advance. - } else { - // assign the next index for this item. - result->_map[oldIndex] = newIndex++; - } - } - }]; - return result; -} - -- (NSInteger)integerForKey:(NSInteger)key -{ - if (_isIdentity) { - return key; - } else if (_isEmpty) { - return NSNotFound; - } - - const auto result = _map.find(key); - return result != _map.end() ? result->second : NSNotFound; -} - -- (void)setInteger:(NSInteger)value forKey:(NSInteger)key -{ - if (_immutable) { - ASDisplayNodeFailAssert(@"Cannot mutate special integer map: %@", self); - return; - } - - _map[key] = value; -} - -- (ASIntegerMap *)inverseMap -{ - if (_isIdentity || _isEmpty) { - return self; - } - - const auto result = [[ASIntegerMap alloc] init]; - - for (const auto &e : _map) { - result->_map[e.second] = e.first; - } - return result; -} - -#pragma mark - NSCopying - -- (id)copyWithZone:(NSZone *)zone -{ - if (_immutable) { - return self; - } - - const auto newMap = [[ASIntegerMap allocWithZone:zone] init]; - newMap->_map = _map; - return newMap; -} - -#pragma mark - Description - -- (NSMutableArray *)propertiesForDescription -{ - NSMutableArray *result = [NSMutableArray array]; - - if (_isIdentity) { - [result addObject:@{ @"map": @"" }]; - } else if (_isEmpty) { - [result addObject:@{ @"map": @"" }]; - } else { - // { 1->2 3->4 5->6 } - NSMutableString *str = [NSMutableString string]; - for (const auto &e : _map) { - [str appendFormat:@" %ld->%ld", (long)e.first, (long)e.second]; - } - // Remove leading space - if (str.length > 0) { - [str deleteCharactersInRange:NSMakeRange(0, 1)]; - } - [result addObject:@{ @"map": str }]; - } - - return result; -} - -- (NSString *)description -{ - return ASObjectDescriptionMakeWithoutObject([self propertiesForDescription]); -} - -- (BOOL)isEqual:(id)object -{ - if ([super isEqual:object]) { - return YES; - } - - if (ASIntegerMap *otherMap = ASDynamicCast(object, ASIntegerMap)) { - return otherMap->_map == _map; - } - return NO; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/Details/ASLayoutController.h b/submodules/AsyncDisplayKit/Source/Details/ASLayoutController.h deleted file mode 100644 index d0e2f60907..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASLayoutController.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// ASLayoutController.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@class ASCollectionElement, ASElementMap; - -struct ASDirectionalScreenfulBuffer { - CGFloat positiveDirection; // Positive relative to iOS Core Animation layer coordinate space. - CGFloat negativeDirection; -}; -typedef struct ASDirectionalScreenfulBuffer ASDirectionalScreenfulBuffer; - -@protocol ASLayoutController - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; - -- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; - -- (NSHashTable *)elementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType map:(ASElementMap *)map; - -- (void)allElementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode displaySet:(NSHashTable * _Nullable * _Nullable)displaySet preloadSet:(NSHashTable * _Nullable * _Nullable)preloadSet map:(ASElementMap *)map; - -@optional - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Details/ASLayoutRangeType.h b/submodules/AsyncDisplayKit/Source/Details/ASLayoutRangeType.h deleted file mode 100644 index d4af470c06..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASLayoutRangeType.h +++ /dev/null @@ -1,68 +0,0 @@ -// -// ASLayoutRangeType.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -typedef struct { - CGFloat leadingBufferScreenfuls; - CGFloat trailingBufferScreenfuls; -} ASRangeTuningParameters; - -AS_EXTERN ASRangeTuningParameters const ASRangeTuningParametersZero; - -AS_EXTERN BOOL ASRangeTuningParametersEqualToRangeTuningParameters(ASRangeTuningParameters lhs, ASRangeTuningParameters rhs); - -/** - * Each mode has a complete set of tuning parameters for range types. - * Depending on some conditions (including interface state and direction of the scroll view, state of rendering engine, etc), - * a range controller can choose which mode it should use at a given time. - */ -typedef NS_ENUM(NSInteger, ASLayoutRangeMode) { - ASLayoutRangeModeUnspecified = -1, - - /** - * Minimum mode is used when a range controller should limit the amount of work it performs. - * Thus, fewer views/layers are created and less data is fetched, saving system resources. - * Range controller can automatically switch to full mode when conditions change. - */ - ASLayoutRangeModeMinimum = 0, - - /** - * Normal/Full mode that a range controller uses to provide the best experience for end users. - * This mode is usually used for an active scroll view. - * A range controller under this requires more resources compare to minimum mode. - */ - ASLayoutRangeModeFull, - - /** - * Visible Only mode is used when a range controller should set its display and preload regions to only the size of their bounds. - * This causes all additional backing stores & preloaded data to be released, while ensuring a user revisiting the view will - * still be able to see the expected content. This mode is automatically set on all ASRangeControllers when the app suspends, - * allowing the operating system to keep the app alive longer and increase the chance it is still warm when the user returns. - */ - ASLayoutRangeModeVisibleOnly, - - /** - * Low Memory mode is used when a range controller should discard ALL graphics buffers, including for the area that would be visible - * the next time the user views it (bounds). The only range it preserves is Preload, which is limited to the bounds, allowing - * the content to be restored relatively quickly by re-decoding images (the compressed images are ~10% the size of the decoded ones, - * and text is a tiny fraction of its rendered size). - */ - ASLayoutRangeModeLowMemory -}; - -static NSInteger const ASLayoutRangeModeCount = 4; - -typedef NS_ENUM(NSInteger, ASLayoutRangeType) { - ASLayoutRangeTypeDisplay, - ASLayoutRangeTypePreload -}; - -static NSInteger const ASLayoutRangeTypeCount = 2; diff --git a/submodules/AsyncDisplayKit/Source/Details/ASMutableAttributedStringBuilder.h b/submodules/AsyncDisplayKit/Source/Details/ASMutableAttributedStringBuilder.h deleted file mode 100644 index d531549a0e..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASMutableAttributedStringBuilder.h +++ /dev/null @@ -1,65 +0,0 @@ -// -// ASMutableAttributedStringBuilder.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -/* - * Use this class to compose new attributed strings. You may use the normal - * attributed string calls on this the same way you would on a normal mutable - * attributed string, but it coalesces your changes into transactions on the - * actual string allowing improvements in performance. - * - * @discussion This is a use-once and throw away class for each string you make. - * Since this class is designed for increasing performance, we actually hand - * back the internally managed mutable attributed string in the - * `composedAttributedString` call. So once you make that call, any more - * changes will actually modify the string that was handed back to you in that - * method. - * - * Combination of multiple calls into single attribution is managed through - * merging of attribute dictionaries over ranges. For best performance, call - * collections of attributions over a single range together. So for instance, - * don't call addAttributes for range1, then range2, then range1 again. Group - * them together so you call addAttributes for both range1 together, and then - * range2. - * - * Also please note that switching between addAttribute and setAttributes in the - * middle of composition is a bad idea for performance because they have - * semantically different meanings, and trigger a commit of the pending - * attributes. - * - * Please note that ALL of the standard NSString methods are left unimplemented. - */ -AS_SUBCLASSING_RESTRICTED -@interface ASMutableAttributedStringBuilder : NSMutableAttributedString - -- (instancetype)initWithString:(NSString *)str attributes:(nullable NSDictionary *)attrs; -- (instancetype)initWithAttributedString:(NSAttributedString *)attrStr; - -- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str; -- (void)setAttributes:(nullable NSDictionary *)attrs range:(NSRange)range; - -- (void)addAttribute:(NSString *)name value:(id)value range:(NSRange)range; -- (void)addAttributes:(NSDictionary *)attrs range:(NSRange)range; -- (void)removeAttribute:(NSString *)name range:(NSRange)range; - -- (void)replaceCharactersInRange:(NSRange)range withAttributedString:(NSAttributedString *)attrString; -- (void)insertAttributedString:(NSAttributedString *)attrString atIndex:(NSUInteger)loc; -- (void)appendAttributedString:(NSAttributedString *)attrString; -- (void)deleteCharactersInRange:(NSRange)range; -- (void)setAttributedString:(NSAttributedString *)attrString; - -- (NSMutableAttributedString *)composedAttributedString; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Details/ASMutableAttributedStringBuilder.mm b/submodules/AsyncDisplayKit/Source/Details/ASMutableAttributedStringBuilder.mm deleted file mode 100644 index b393fe1118..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASMutableAttributedStringBuilder.mm +++ /dev/null @@ -1,254 +0,0 @@ -// -// ASMutableAttributedStringBuilder.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@implementation ASMutableAttributedStringBuilder { - // Flag for the type of the current transaction (set or add) - BOOL _setRange; - // The range over which the currently pending transaction will occur - NSRange _pendingRange; - // The actual attribute dictionary that is being composed - NSMutableDictionary *_pendingRangeAttributes; - NSMutableAttributedString *_attrStr; - - // We delay initialization of the _attrStr until we need to - NSString *_initString; -} - -- (instancetype)init -{ - if (self = [super init]) { - _attrStr = [[NSMutableAttributedString alloc] init]; - _pendingRange.location = NSNotFound; - } - return self; -} - -- (instancetype)initWithString:(NSString *)str -{ - return [self initWithString:str attributes:@{}]; -} - -- (instancetype)initWithString:(NSString *)str attributes:(NSDictionary *)attrs -{ - if (self = [super init]) { - // We cache this in an ivar that we can lazily construct the attributed - // string with when we get to a forced commit point. - _initString = str; - // Triggers a creation of the _pendingRangeAttributes dictionary which then - // is filled with entries from the given attrs dict. - [[self _pendingRangeAttributes] addEntriesFromDictionary:attrs]; - _setRange = NO; - _pendingRange = NSMakeRange(0, _initString.length); - } - return self; -} - -- (instancetype)initWithAttributedString:(NSAttributedString *)attrStr -{ - if (self = [super init]) { - _attrStr = [[NSMutableAttributedString alloc] initWithAttributedString:attrStr]; - _pendingRange.location = NSNotFound; - } - return self; -} - -- (NSMutableAttributedString *)_attributedString -{ - if (_attrStr == nil && _initString != nil) { - // We can lazily construct the attributed string if it hasn't already been - // created with the existing pending attributes. This is significantly - // faster if more attributes are added after initializing this instance - // and the new attributions are for the entire string anyway. - _attrStr = [[NSMutableAttributedString alloc] initWithString:_initString attributes:_pendingRangeAttributes]; - _pendingRangeAttributes = nil; - _pendingRange.location = NSNotFound; - _initString = nil; - } - - return _attrStr; -} - -#pragma mark - Pending attribution - -- (NSMutableDictionary *)_pendingRangeAttributes -{ - // Lazy dictionary creation. Call this if you want to force initialization, - // otherwise just use the ivar. - if (_pendingRangeAttributes == nil) { - _pendingRangeAttributes = [[NSMutableDictionary alloc] init]; - } - return _pendingRangeAttributes; -} - -- (void)_applyPendingRangeAttributions -{ - if (_attrStr == nil) { - // Trigger its creation if it doesn't exist. - [self _attributedString]; - } - - if (_pendingRangeAttributes.count == 0) { - return; - } - - if (_pendingRange.location == NSNotFound) { - return; - } - - if (_setRange) { - [[self _attributedString] setAttributes:_pendingRangeAttributes range:_pendingRange]; - } else { - [[self _attributedString] addAttributes:_pendingRangeAttributes range:_pendingRange]; - } - _pendingRangeAttributes = nil; - _pendingRange.location = NSNotFound; -} - -#pragma mark - Editing - -- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str -{ - [self _applyPendingRangeAttributions]; - [[self _attributedString] replaceCharactersInRange:range withString:str]; -} - -- (void)replaceCharactersInRange:(NSRange)range withAttributedString:(NSAttributedString *)attrString -{ - [self _applyPendingRangeAttributions]; - [[self _attributedString] replaceCharactersInRange:range withAttributedString:attrString]; -} - -- (void)addAttribute:(NSString *)name value:(id)value range:(NSRange)range -{ - if (_setRange) { - [self _applyPendingRangeAttributions]; - _setRange = NO; - } - - if (!NSEqualRanges(_pendingRange, range)) { - [self _applyPendingRangeAttributions]; - _pendingRange = range; - } - - NSMutableDictionary *pendingAttributes = [self _pendingRangeAttributes]; - pendingAttributes[name] = value; -} - -- (void)addAttributes:(NSDictionary *)attrs range:(NSRange)range -{ - if (_setRange) { - [self _applyPendingRangeAttributions]; - _setRange = NO; - } - - if (!NSEqualRanges(_pendingRange, range)) { - [self _applyPendingRangeAttributions]; - _pendingRange = range; - } - - NSMutableDictionary *pendingAttributes = [self _pendingRangeAttributes]; - [pendingAttributes addEntriesFromDictionary:attrs]; -} - -- (void)insertAttributedString:(NSAttributedString *)attrString atIndex:(NSUInteger)loc -{ - [self _applyPendingRangeAttributions]; - [[self _attributedString] insertAttributedString:attrString atIndex:loc]; -} - -- (void)appendAttributedString:(NSAttributedString *)attrString -{ - [self _applyPendingRangeAttributions]; - [[self _attributedString] appendAttributedString:attrString]; -} - -- (void)deleteCharactersInRange:(NSRange)range -{ - [self _applyPendingRangeAttributions]; - [[self _attributedString] deleteCharactersInRange:range]; -} - -- (void)setAttributedString:(NSAttributedString *)attrString -{ - [self _applyPendingRangeAttributions]; - [[self _attributedString] setAttributedString:attrString]; -} - -- (void)setAttributes:(NSDictionary *)attrs range:(NSRange)range -{ - if (!_setRange) { - [self _applyPendingRangeAttributions]; - _setRange = YES; - } - - if (!NSEqualRanges(_pendingRange, range)) { - [self _applyPendingRangeAttributions]; - _pendingRange = range; - } - - NSMutableDictionary *pendingAttributes = [self _pendingRangeAttributes]; - [pendingAttributes addEntriesFromDictionary:attrs]; -} - -- (void)removeAttribute:(NSString *)name range:(NSRange)range -{ - // This call looks like the other set/add functions, but in order for this - // function to perform as advertised we MUST first add the attributes we - // currently have pending. - [self _applyPendingRangeAttributions]; - - [[self _attributedString] removeAttribute:name range:range]; -} - -#pragma mark - Output - -- (NSMutableAttributedString *)composedAttributedString -{ - if (_pendingRangeAttributes.count > 0) { - [self _applyPendingRangeAttributions]; - } - return [self _attributedString]; -} - -#pragma mark - Forwarding - -- (NSUInteger)length -{ - // If we just want a length call, no need to lazily construct the attributed string - return _attrStr ? _attrStr.length : _initString.length; -} - -- (NSDictionary *)attributesAtIndex:(NSUInteger)location effectiveRange:(NSRangePointer)range -{ - return [[self _attributedString] attributesAtIndex:location effectiveRange:range]; -} - -- (NSString *)string -{ - return _attrStr ? _attrStr.string : _initString; -} - -- (NSMutableString *)mutableString -{ - return [[self _attributedString] mutableString]; -} - -- (void)beginEditing -{ - [[self _attributedString] beginEditing]; -} - -- (void)endEditing -{ - [[self _attributedString] endEditing]; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/Details/ASPINRemoteImageDownloader.h b/submodules/AsyncDisplayKit/Source/Details/ASPINRemoteImageDownloader.h deleted file mode 100644 index dceca079e2..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASPINRemoteImageDownloader.h +++ /dev/null @@ -1,84 +0,0 @@ -// -// ASPINRemoteImageDownloader.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -#if AS_PIN_REMOTE_IMAGE - -#import - -NS_ASSUME_NONNULL_BEGIN - -@class PINRemoteImageManager; -@protocol PINRemoteImageCaching; - -@interface ASPINRemoteImageDownloader : NSObject - -/** - * A shared image downloader which can be used by @c ASNetworkImageNodes and @c ASMultiplexImageNodes. - * The userInfo provided by this downloader is an instance of `PINRemoteImageManagerResult`. - * - * This is the default downloader used by network backed image nodes if PINRemoteImage and PINCache are - * available. It uses PINRemoteImage's features to provide caching and progressive image downloads. - */ -+ (ASPINRemoteImageDownloader *)sharedDownloader NS_RETURNS_RETAINED; - -/** - * Sets the default NSURLSessionConfiguration that will be used by @c ASNetworkImageNodes and @c ASMultiplexImageNodes - * while loading images off the network. This must be specified early in the application lifecycle before - * `sharedDownloader` is accessed. - * - * @param configuration The session configuration that will be used by `sharedDownloader` - * - */ -+ (void)setSharedImageManagerWithConfiguration:(nullable NSURLSessionConfiguration *)configuration; - -/** - * Sets the default NSURLSessionConfiguration that will be used by @c ASNetworkImageNodes and @c ASMultiplexImageNodes - * while loading images off the network. This must be specified early in the application lifecycle before - * `sharedDownloader` is accessed. - * - * @param configuration The session configuration that will be used by `sharedDownloader` - * @param imageCache The cache to be used by PINRemoteImage - nil will set up a default cache: PINCache - * if it is available, PINRemoteImageBasicCache (NSCache) if not. - * - */ -+ (void)setSharedImageManagerWithConfiguration:(nullable NSURLSessionConfiguration *)configuration - imageCache:(nullable id)imageCache; - -/** - * Sets a custom preconfigured PINRemoteImageManager that will be used by @c ASNetworkImageNodes and @c ASMultiplexImageNodes - * while loading images off the network. This must be specified early in the application lifecycle before - * `sharedDownloader` is accessed. - * - * @param preconfiguredPINRemoteImageManager The preconfigured remote image manager that will be used by `sharedDownloader` - */ -+ (void)setSharedPreconfiguredRemoteImageManager:(PINRemoteImageManager *)preconfiguredPINRemoteImageManager; - -/** - * The shared instance of a @c PINRemoteImageManager used by all @c ASPINRemoteImageDownloaders - * - * @discussion you can use this method to access the shared manager. This is useful to share a cache - * and resources if you need to download images outside of an @c ASNetworkImageNode or - * @c ASMultiplexImageNode. It's also useful to access the memoryCache and diskCache to set limits - * or handle authentication challenges. - * - * @return An instance of a @c PINRemoteImageManager - */ -- (PINRemoteImageManager *)sharedPINRemoteImageManager; - -@end - -NS_ASSUME_NONNULL_END - -#endif - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASPINRemoteImageDownloader.mm b/submodules/AsyncDisplayKit/Source/Details/ASPINRemoteImageDownloader.mm deleted file mode 100644 index 84341b40c8..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASPINRemoteImageDownloader.mm +++ /dev/null @@ -1,393 +0,0 @@ -// -// ASPINRemoteImageDownloader.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -#if AS_PIN_REMOTE_IMAGE -#import - -#import -#import -#import - -#if __has_include () -#define PIN_ANIMATED_AVAILABLE 1 -#import -#import -#else -#define PIN_ANIMATED_AVAILABLE 0 -#endif - -#if __has_include() -#define PIN_WEBP_AVAILABLE 1 -#else -#define PIN_WEBP_AVAILABLE 0 -#endif - -#import -#import -#import - -static inline PINRemoteImageManagerPriority PINRemoteImageManagerPriorityWithASImageDownloaderPriority(ASImageDownloaderPriority priority) { - switch (priority) { - case ASImageDownloaderPriorityPreload: - return PINRemoteImageManagerPriorityLow; - - case ASImageDownloaderPriorityImminent: - return PINRemoteImageManagerPriorityDefault; - - case ASImageDownloaderPriorityVisible: - return PINRemoteImageManagerPriorityHigh; - } -} - -#if PIN_ANIMATED_AVAILABLE - -@interface ASPINRemoteImageDownloader () -@end - -@interface PINCachedAnimatedImage (ASPINRemoteImageDownloader) -@end - -@implementation PINCachedAnimatedImage (ASPINRemoteImageDownloader) - -- (BOOL)isDataSupported:(NSData *)data -{ - if ([data pin_isGIF]) { - return YES; - } -#if PIN_WEBP_AVAILABLE - else if ([data pin_isAnimatedWebP]) { - return YES; - } -#endif - return NO; -} - -@end -#endif - -// Declare two key methods on PINCache objects, avoiding a direct dependency on PINCache.h -@protocol ASPINCache -- (id)diskCache; -@end - -@protocol ASPINDiskCache -@property NSUInteger byteLimit; -@end - -@interface ASPINRemoteImageManager : PINRemoteImageManager -@end - -@implementation ASPINRemoteImageManager - -//Share image cache with sharedImageManager image cache. -+ (id )defaultImageCache -{ - static dispatch_once_t onceToken; - static id cache = nil; - dispatch_once(&onceToken, ^{ - cache = [[PINRemoteImageManager sharedImageManager] cache]; - if ([cache respondsToSelector:@selector(diskCache)]) { - id diskCache = [(id )cache diskCache]; - if ([diskCache respondsToSelector:@selector(setByteLimit:)]) { - // Set a default byteLimit. PINCache recently implemented a 50MB default (PR #201). - // Ensure that older versions of PINCache also have a byteLimit applied. - // NOTE: Using 20MB limit while large cache initialization is being optimized (Issue #144). - ((id )diskCache).byteLimit = 20 * 1024 * 1024; - } - } - }); - return cache; -} - -@end - -static ASPINRemoteImageDownloader *sharedDownloader = nil; -static PINRemoteImageManager *sharedPINRemoteImageManager = nil; - -@interface ASPINRemoteImageDownloader () -@end - -@implementation ASPINRemoteImageDownloader - -+ (ASPINRemoteImageDownloader *)sharedDownloader NS_RETURNS_RETAINED -{ - static dispatch_once_t onceToken = 0; - dispatch_once(&onceToken, ^{ - sharedDownloader = [[ASPINRemoteImageDownloader alloc] init]; - }); - return sharedDownloader; -} - -+ (void)setSharedImageManagerWithConfiguration:(nullable NSURLSessionConfiguration *)configuration -{ - NSAssert(sharedDownloader == nil, @"Singleton has been created and session can no longer be configured."); - PINRemoteImageManager *sharedManager = [self PINRemoteImageManagerWithConfiguration:configuration imageCache:nil]; - [self setSharedPreconfiguredRemoteImageManager:sharedManager]; -} - -+ (void)setSharedImageManagerWithConfiguration:(nullable NSURLSessionConfiguration *)configuration - imageCache:(nullable id)imageCache -{ - NSAssert(sharedDownloader == nil, @"Singleton has been created and session can no longer be configured."); - PINRemoteImageManager *sharedManager = [self PINRemoteImageManagerWithConfiguration:configuration imageCache:imageCache]; - [self setSharedPreconfiguredRemoteImageManager:sharedManager]; -} - -static dispatch_once_t shared_init_predicate; - -+ (void)setSharedPreconfiguredRemoteImageManager:(PINRemoteImageManager *)preconfiguredPINRemoteImageManager -{ - NSAssert(preconfiguredPINRemoteImageManager != nil, @"setSharedPreconfiguredRemoteImageManager requires a non-nil parameter"); - NSAssert1(sharedPINRemoteImageManager == nil, @"An instance of %@ has been set. Either configuration or preconfigured image manager can be set at a time and only once.", [[sharedPINRemoteImageManager class] description]); - - dispatch_once(&shared_init_predicate, ^{ - sharedPINRemoteImageManager = preconfiguredPINRemoteImageManager; - }); -} - -+ (PINRemoteImageManager *)PINRemoteImageManagerWithConfiguration:(nullable NSURLSessionConfiguration *)configuration imageCache:(nullable id)imageCache -{ - PINRemoteImageManager *manager = nil; -#if DEBUG - // Check that Carthage users have linked both PINRemoteImage & PINCache by testing for one file each - if (!(NSClassFromString(@"PINRemoteImageManager"))) { - NSException *e = [NSException - exceptionWithName:@"FrameworkSetupException" - reason:@"Missing the path to the PINRemoteImage framework." - userInfo:nil]; - @throw e; - } - if (!(NSClassFromString(@"PINCache"))) { - NSException *e = [NSException - exceptionWithName:@"FrameworkSetupException" - reason:@"Missing the path to the PINCache framework." - userInfo:nil]; - @throw e; - } -#endif -#if PIN_ANIMATED_AVAILABLE - manager = [[ASPINRemoteImageManager alloc] initWithSessionConfiguration:configuration - alternativeRepresentationProvider:[self sharedDownloader] - imageCache:imageCache]; -#else - manager = [[ASPINRemoteImageManager alloc] initWithSessionConfiguration:configuration - alternativeRepresentationProvider:nil - imageCache:imageCache]; -#endif - return manager; -} - -- (PINRemoteImageManager *)sharedPINRemoteImageManager -{ - dispatch_once(&shared_init_predicate, ^{ - sharedPINRemoteImageManager = [ASPINRemoteImageDownloader PINRemoteImageManagerWithConfiguration:nil imageCache:nil]; - }); - return sharedPINRemoteImageManager; -} - -- (BOOL)sharedImageManagerSupportsMemoryRemoval -{ - static BOOL sharedImageManagerSupportsMemoryRemoval = NO; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sharedImageManagerSupportsMemoryRemoval = [[[self sharedPINRemoteImageManager] cache] respondsToSelector:@selector(removeObjectForKeyFromMemory:)]; - }); - return sharedImageManagerSupportsMemoryRemoval; -} - -#pragma mark ASImageProtocols - -#if PIN_ANIMATED_AVAILABLE -- (nullable id )animatedImageWithData:(NSData *)animatedImageData -{ - return [[PINCachedAnimatedImage alloc] initWithAnimatedImageData:animatedImageData]; -} -#endif - -- (id )synchronouslyFetchedCachedImageWithURL:(NSURL *)URL; -{ - PINRemoteImageManager *manager = [self sharedPINRemoteImageManager]; - PINRemoteImageManagerResult *result = [manager synchronousImageFromCacheWithURL:URL processorKey:nil options:PINRemoteImageManagerDownloadOptionsSkipDecode]; - -#if PIN_ANIMATED_AVAILABLE - if (result.alternativeRepresentation) { - return result.alternativeRepresentation; - } -#endif - return result.image; -} - -- (void)cachedImageWithURL:(NSURL *)URL - callbackQueue:(dispatch_queue_t)callbackQueue - completion:(ASImageCacherCompletion)completion -{ - [[self sharedPINRemoteImageManager] imageFromCacheWithURL:URL processorKey:nil options:PINRemoteImageManagerDownloadOptionsSkipDecode completion:^(PINRemoteImageManagerResult * _Nonnull result) { - [ASPINRemoteImageDownloader _performWithCallbackQueue:callbackQueue work:^{ -#if PIN_ANIMATED_AVAILABLE - if (result.alternativeRepresentation) { - completion(result.alternativeRepresentation); - } else { - completion(result.image); - } -#else - completion(result.image); -#endif - }]; - }]; -} - -- (void)clearFetchedImageFromCacheWithURL:(NSURL *)URL -{ - if ([self sharedImageManagerSupportsMemoryRemoval]) { - PINRemoteImageManager *manager = [self sharedPINRemoteImageManager]; - NSString *key = [manager cacheKeyForURL:URL processorKey:nil]; - [[manager cache] removeObjectForKeyFromMemory:key]; - } -} - -- (nullable id)downloadImageWithURL:(NSURL *)URL - callbackQueue:(dispatch_queue_t)callbackQueue - downloadProgress:(ASImageDownloaderProgress)downloadProgress - completion:(ASImageDownloaderCompletion)completion; -{ - return [self downloadImageWithURL:URL - priority:ASImageDownloaderPriorityImminent // maps to default priority - callbackQueue:callbackQueue - downloadProgress:downloadProgress - completion:completion]; -} - -- (nullable id)downloadImageWithURL:(NSURL *)URL - priority:(ASImageDownloaderPriority)priority - callbackQueue:(dispatch_queue_t)callbackQueue - downloadProgress:(ASImageDownloaderProgress)downloadProgress - completion:(ASImageDownloaderCompletion)completion -{ - PINRemoteImageManagerPriority pi_priority = PINRemoteImageManagerPriorityWithASImageDownloaderPriority(priority); - - PINRemoteImageManagerProgressDownload progressDownload = ^(int64_t completedBytes, int64_t totalBytes) { - if (downloadProgress == nil) { return; } - - [ASPINRemoteImageDownloader _performWithCallbackQueue:callbackQueue work:^{ - downloadProgress(completedBytes / (CGFloat)totalBytes); - }]; - }; - - PINRemoteImageManagerImageCompletion imageCompletion = ^(PINRemoteImageManagerResult * _Nonnull result) { - [ASPINRemoteImageDownloader _performWithCallbackQueue:callbackQueue work:^{ -#if PIN_ANIMATED_AVAILABLE - if (result.alternativeRepresentation) { - completion(result.alternativeRepresentation, result.error, result.UUID, result); - } else { - completion(result.image, result.error, result.UUID, result); - } -#else - completion(result.image, result.error, result.UUID, result); -#endif - }]; - }; - - // add "IgnoreCache" option since we have a caching API so we already checked it, not worth checking again. - // PINRemoteImage is responsible for coalescing downloads, and even if it wasn't, the tiny probability of - // extra downloads isn't worth the effort of rechecking caches every single time. In order to provide - // feedback to the consumer about whether images are cached, we can't simply make the cache a no-op and - // check the cache as part of this download. - return [[self sharedPINRemoteImageManager] downloadImageWithURL:URL - options:PINRemoteImageManagerDownloadOptionsSkipDecode | PINRemoteImageManagerDownloadOptionsIgnoreCache - priority:pi_priority - progressImage:nil - progressDownload:progressDownload - completion:imageCompletion]; -} - -- (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier -{ - ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); - [[self sharedPINRemoteImageManager] cancelTaskWithUUID:downloadIdentifier storeResumeData:NO]; -} - -- (void)cancelImageDownloadWithResumePossibilityForIdentifier:(id)downloadIdentifier -{ - ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); - [[self sharedPINRemoteImageManager] cancelTaskWithUUID:downloadIdentifier storeResumeData:YES]; -} - -- (void)setProgressImageBlock:(ASImageDownloaderProgressImage)progressBlock callbackQueue:(dispatch_queue_t)callbackQueue withDownloadIdentifier:(id)downloadIdentifier -{ - ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); - - if (progressBlock) { - [[self sharedPINRemoteImageManager] setProgressImageCallback:^(PINRemoteImageManagerResult * _Nonnull result) { - dispatch_async(callbackQueue, ^{ - progressBlock(result.image, result.renderedImageQuality, result.UUID); - }); - } ofTaskWithUUID:downloadIdentifier]; - } else { - [[self sharedPINRemoteImageManager] setProgressImageCallback:nil ofTaskWithUUID:downloadIdentifier]; - } -} - -- (void)setPriority:(ASImageDownloaderPriority)priority withDownloadIdentifier:(id)downloadIdentifier -{ - ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); - - PINRemoteImageManagerPriority pi_priority = PINRemoteImageManagerPriorityWithASImageDownloaderPriority(priority); - [[self sharedPINRemoteImageManager] setPriority:pi_priority ofTaskWithUUID:downloadIdentifier]; -} - -#pragma mark - PINRemoteImageManagerAlternateRepresentationProvider - -- (id)alternateRepresentationWithData:(NSData *)data options:(PINRemoteImageManagerDownloadOptions)options -{ -#if PIN_ANIMATED_AVAILABLE - if ([data pin_isAnimatedGIF]) { - return data; - } -#if PIN_WEBP_AVAILABLE - else if ([data pin_isAnimatedWebP]) { - return data; - } -#endif - -#endif - return nil; -} - -#pragma mark - Private - -/** - * If on main thread and queue is main, perform now. - * If queue is nil, assert and perform now. - * Otherwise, dispatch async to queue. - */ -+ (void)_performWithCallbackQueue:(dispatch_queue_t)queue work:(void (^)(void))work -{ - if (work == nil) { - // No need to assert here, really. We aren't expecting any feedback from this method. - return; - } - - if (ASDisplayNodeThreadIsMain() && queue == dispatch_get_main_queue()) { - work(); - } else if (queue == nil) { - ASDisplayNodeFailAssert(@"Callback queue should not be nil."); - work(); - } else { - dispatch_async(queue, work); - } -} - -@end -#endif - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASPageTable.h b/submodules/AsyncDisplayKit/Source/Details/ASPageTable.h deleted file mode 100644 index 7e01e933a6..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASPageTable.h +++ /dev/null @@ -1,124 +0,0 @@ -// -// ASPageTable.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import -#import -#import - -@class ASCollectionElement; - -NS_ASSUME_NONNULL_BEGIN - -/** - * Represents x and y coordinates of a page. - */ -typedef uintptr_t ASPageCoordinate; - -/** - * Returns a page coordinate with the given x and y values. Both of them must be less than 65,535. - */ -AS_EXTERN ASPageCoordinate ASPageCoordinateMake(uint16_t x, uint16_t y) AS_WARN_UNUSED_RESULT; - -/** - * Returns coordinate of the page that contains the specified point. - * Similar to CGRectContainsPoint, a point is considered inside a page if its lie inside the page or on the minimum X or minimum Y edge. - * - * @param point The point that the page at the returned should contain. Any negative of the point will be corrected to 0.0 - * - * @param pageSize The size of each page. - */ -AS_EXTERN ASPageCoordinate ASPageCoordinateForPageThatContainsPoint(CGPoint point, CGSize pageSize) AS_WARN_UNUSED_RESULT; - -AS_EXTERN uint16_t ASPageCoordinateGetX(ASPageCoordinate pageCoordinate) AS_WARN_UNUSED_RESULT; - -AS_EXTERN uint16_t ASPageCoordinateGetY(ASPageCoordinate pageCoordinate) AS_WARN_UNUSED_RESULT; - -AS_EXTERN CGRect ASPageCoordinateGetPageRect(ASPageCoordinate pageCoordinate, CGSize pageSize) AS_WARN_UNUSED_RESULT; - -/** - * Returns coordinate pointers for pages that intersect the specified rect. For each pointer, use ASPageCoordinateFromPointer() to get the original coordinate. - * The specified rect is restricted to the bounds of a content rect that has an origin of {0, 0} and a size of the given contentSize. - * - * @param rect The rect intersecting the target pages. - * - * @param contentSize The combined size of all pages. - * - * @param pageSize The size of each page. - */ -AS_EXTERN NSPointerArray * _Nullable ASPageCoordinatesForPagesThatIntersectRect(CGRect rect, CGSize contentSize, CGSize pageSize) AS_WARN_UNUSED_RESULT; - -/** - * An alias for an NSMapTable created to store objects using ASPageCoordinates as keys. - * - * You should not call -objectForKey:, -setObject:forKey:, or -removeObjectForKey: - * on these objects. - */ -typedef NSMapTable ASPageTable; - -/** - * A page to array of layout attributes table. - */ -typedef ASPageTable *> ASPageToLayoutAttributesTable; - -/** - * A category for creating & using map tables meant for storing objects using ASPage as keys. - */ -@interface NSMapTable (ASPageTableMethods) - -/** - * Creates a new page table with (NSMapTableStrongMemory | NSMapTableObjectPointerPersonality) for values. - */ -+ (ASPageTable *)pageTableForStrongObjectPointers NS_RETURNS_RETAINED; - -/** - * Creates a new page table with (NSMapTableWeakMemory | NSMapTableObjectPointerPersonality) for values. - */ -+ (ASPageTable *)pageTableForWeakObjectPointers NS_RETURNS_RETAINED; - -/** - * Builds a new page to layout attributes from the given layout attributes. - * - * @param layoutAttributesEnumerator The layout attributes to build from - * - * @param contentSize The combined size of all pages. - * - * @param pageSize The size of each page. - */ -+ (ASPageToLayoutAttributesTable *)pageTableWithLayoutAttributes:(id)layoutAttributesEnumerator contentSize:(CGSize)contentSize pageSize:(CGSize)pageSize NS_RETURNS_RETAINED; - -/** - * Retrieves the object for a given page, or nil if the page is not found. - * - * @param page A page to lookup the object for. - */ -- (nullable ObjectType)objectForPage:(ASPageCoordinate)page; - -/** - * Sets the given object for the associated page. - * - * @param object The object to store as value. - * - * @param page The page to use for the rect. - */ -- (void)setObject:(ObjectType)object forPage:(ASPageCoordinate)page; - -/** - * Removes the object for the given page, if one exists. - * - * @param page The page to remove. - */ -- (void)removeObjectForPage:(ASPageCoordinate)page; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASPageTable.mm b/submodules/AsyncDisplayKit/Source/Details/ASPageTable.mm deleted file mode 100644 index af74d0c59e..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASPageTable.mm +++ /dev/null @@ -1,151 +0,0 @@ -// -// ASPageTable.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -ASPageCoordinate ASPageCoordinateMake(uint16_t x, uint16_t y) -{ - // Add 1 to the end result because 0 is not accepted by NSArray and NSMapTable. - // To avoid overflow after adding, x and y can't be UINT16_MAX (0xFFFF) **at the same time**. - // But for API simplification, we enforce the same restriction to both values. - ASDisplayNodeCAssert(x < UINT16_MAX, @"x coordinate must be less than 65,535"); - ASDisplayNodeCAssert(y < UINT16_MAX, @"y coordinate must be less than 65,535"); - return (x << 16) + y + 1; -} - -ASPageCoordinate ASPageCoordinateForPageThatContainsPoint(CGPoint point, CGSize pageSize) -{ - return ASPageCoordinateMake((MAX(0.0, point.x) / pageSize.width), (MAX(0.0, point.y) / pageSize.height)); -} - -uint16_t ASPageCoordinateGetX(ASPageCoordinate pageCoordinate) -{ - return (pageCoordinate - 1) >> 16; -} - -uint16_t ASPageCoordinateGetY(ASPageCoordinate pageCoordinate) -{ - return (pageCoordinate - 1) & ~(0xFFFF<<16); -} - -CGRect ASPageCoordinateGetPageRect(ASPageCoordinate pageCoordinate, CGSize pageSize) -{ - CGFloat pageWidth = pageSize.width; - CGFloat pageHeight = pageSize.height; - return CGRectMake(ASPageCoordinateGetX(pageCoordinate) * pageWidth, ASPageCoordinateGetY(pageCoordinate) * pageHeight, pageWidth, pageHeight); -} - -NSPointerArray *ASPageCoordinatesForPagesThatIntersectRect(CGRect rect, CGSize contentSize, CGSize pageSize) -{ - CGRect contentRect = CGRectMake(0.0, 0.0, contentSize.width, contentSize.height); - // Make sure the specified rect is within contentRect - rect = CGRectIntersection(rect, contentRect); - if (CGRectIsNull(rect) || CGRectIsEmpty(rect)) { - return nil; - } - - NSPointerArray *result = [NSPointerArray pointerArrayWithOptions:(NSPointerFunctionsIntegerPersonality | NSPointerFunctionsOpaqueMemory)]; - - ASPageCoordinate minPage = ASPageCoordinateForPageThatContainsPoint(CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect)), pageSize); - ASPageCoordinate maxPage = ASPageCoordinateForPageThatContainsPoint(CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(rect)), pageSize); - if (minPage == maxPage) { - [result addPointer:(void *)minPage]; - return result; - } - - NSUInteger minX = ASPageCoordinateGetX(minPage); - NSUInteger minY = ASPageCoordinateGetY(minPage); - NSUInteger maxX = ASPageCoordinateGetX(maxPage); - NSUInteger maxY = ASPageCoordinateGetY(maxPage); - - for (NSUInteger x = minX; x <= maxX; x++) { - for (NSUInteger y = minY; y <= maxY; y++) { - ASPageCoordinate page = ASPageCoordinateMake(x, y); - [result addPointer:(void *)page]; - } - } - - return result; -} - -@implementation NSMapTable (ASPageTableMethods) - -+ (instancetype)pageTableWithValuePointerFunctions:(NSPointerFunctions *)valueFuncs NS_RETURNS_RETAINED -{ - static NSPointerFunctions *pageCoordinatesFuncs; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - pageCoordinatesFuncs = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsIntegerPersonality | NSPointerFunctionsOpaqueMemory]; - }); - - return [[NSMapTable alloc] initWithKeyPointerFunctions:pageCoordinatesFuncs valuePointerFunctions:valueFuncs capacity:0]; -} - -+ (ASPageTable *)pageTableForStrongObjectPointers NS_RETURNS_RETAINED -{ - static NSPointerFunctions *strongObjectPointerFuncs; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - strongObjectPointerFuncs = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsStrongMemory]; - }); - return [self pageTableWithValuePointerFunctions:strongObjectPointerFuncs]; -} - -+ (ASPageTable *)pageTableForWeakObjectPointers NS_RETURNS_RETAINED -{ - static NSPointerFunctions *weakObjectPointerFuncs; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - weakObjectPointerFuncs = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsWeakMemory]; - }); - return [self pageTableWithValuePointerFunctions:weakObjectPointerFuncs]; -} - -+ (ASPageToLayoutAttributesTable *)pageTableWithLayoutAttributes:(id)layoutAttributesEnumerator contentSize:(CGSize)contentSize pageSize:(CGSize)pageSize NS_RETURNS_RETAINED -{ - ASPageToLayoutAttributesTable *result = [ASPageTable pageTableForStrongObjectPointers]; - for (UICollectionViewLayoutAttributes *attrs in layoutAttributesEnumerator) { - // This attrs may span multiple pages. Make sure it's registered to all of them - NSPointerArray *pages = ASPageCoordinatesForPagesThatIntersectRect(attrs.frame, contentSize, pageSize); - - for (id pagePtr in pages) { - ASPageCoordinate page = (ASPageCoordinate)pagePtr; - NSMutableArray *attrsInPage = [result objectForPage:page]; - if (attrsInPage == nil) { - attrsInPage = [[NSMutableArray alloc] init]; - [result setObject:attrsInPage forPage:page]; - } - [attrsInPage addObject:attrs]; - } - } - return result; -} - -- (id)objectForPage:(ASPageCoordinate)page -{ - __unsafe_unretained id key = (__bridge id)(void *)page; - return [self objectForKey:key]; -} - -- (void)setObject:(id)object forPage:(ASPageCoordinate)page -{ - __unsafe_unretained id key = (__bridge id)(void *)page; - [self setObject:object forKey:key]; -} - -- (void)removeObjectForPage:(ASPageCoordinate)page -{ - __unsafe_unretained id key = (__bridge id)(void *)page; - [self removeObjectForKey:key]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASPhotosFrameworkImageRequest.h b/submodules/AsyncDisplayKit/Source/Details/ASPhotosFrameworkImageRequest.h deleted file mode 100644 index 2ca24bfbee..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASPhotosFrameworkImageRequest.h +++ /dev/null @@ -1,73 +0,0 @@ -// -// ASPhotosFrameworkImageRequest.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_USE_PHOTOS - -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -AS_EXTERN NSString *const ASPhotosURLScheme; - -/** - @abstract Use ASPhotosFrameworkImageRequest to encapsulate all the information needed to request an image from - the Photos framework and store it in a URL. - */ -API_AVAILABLE(ios(8.0), tvos(10.0)) -@interface ASPhotosFrameworkImageRequest : NSObject - -- (instancetype)initWithAssetIdentifier:(NSString *)assetIdentifier NS_DESIGNATED_INITIALIZER; - -/** - @return A new image request deserialized from `url`, or nil if `url` is not a valid photos URL. - */ -+ (nullable ASPhotosFrameworkImageRequest *)requestWithURL:(NSURL *)url; - -/** - @abstract The asset identifier for this image request provided during initialization. - */ -@property (nonatomic, readonly) NSString *assetIdentifier; - -/** - @abstract The target size for this image request. Defaults to `PHImageManagerMaximumSize`. - */ -@property (nonatomic) CGSize targetSize; - -/** - @abstract The content mode for this image request. Defaults to `PHImageContentModeDefault`. - - @see `PHImageManager` - */ -@property (nonatomic) PHImageContentMode contentMode; - -/** - @abstract The options specified for this request. Default value is the result of `[PHImageRequestOptions new]`. - - @discussion Some properties of this object are ignored when converting this request into a URL. - As of iOS SDK 9.0, these properties are `progressHandler` and `synchronous`. - */ -@property (nonatomic) PHImageRequestOptions *options; - -/** - @return A new URL converted from this request. - */ -@property (nonatomic, readonly) NSURL *url; - -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)new NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END - -#endif // AS_USE_PHOTOS diff --git a/submodules/AsyncDisplayKit/Source/Details/ASPhotosFrameworkImageRequest.h.orig b/submodules/AsyncDisplayKit/Source/Details/ASPhotosFrameworkImageRequest.h.orig deleted file mode 100644 index bf52121f9e..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASPhotosFrameworkImageRequest.h.orig +++ /dev/null @@ -1,81 +0,0 @@ -// -// ASPhotosFrameworkImageRequest.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -<<<<<<< HEAD -#ifndef MINIMAL_ASDK -======= -#import - -#if AS_USE_PHOTOS - ->>>>>>> 565da7d4935740d12fc204aa061faf093831da1e -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -AS_EXTERN NSString *const ASPhotosURLScheme; - -/** - @abstract Use ASPhotosFrameworkImageRequest to encapsulate all the information needed to request an image from - the Photos framework and store it in a URL. - */ -API_AVAILABLE(ios(8.0), tvos(10.0)) -@interface ASPhotosFrameworkImageRequest : NSObject - -- (instancetype)initWithAssetIdentifier:(NSString *)assetIdentifier NS_DESIGNATED_INITIALIZER; - -/** - @return A new image request deserialized from `url`, or nil if `url` is not a valid photos URL. - */ -+ (nullable ASPhotosFrameworkImageRequest *)requestWithURL:(NSURL *)url; - -/** - @abstract The asset identifier for this image request provided during initialization. - */ -@property (nonatomic, readonly) NSString *assetIdentifier; - -/** - @abstract The target size for this image request. Defaults to `PHImageManagerMaximumSize`. - */ -@property (nonatomic) CGSize targetSize; - -/** - @abstract The content mode for this image request. Defaults to `PHImageContentModeDefault`. - - @see `PHImageManager` - */ -@property (nonatomic) PHImageContentMode contentMode; - -/** - @abstract The options specified for this request. Default value is the result of `[PHImageRequestOptions new]`. - - @discussion Some properties of this object are ignored when converting this request into a URL. - As of iOS SDK 9.0, these properties are `progressHandler` and `synchronous`. - */ -@property (nonatomic) PHImageRequestOptions *options; - -/** - @return A new URL converted from this request. - */ -@property (nonatomic, readonly) NSURL *url; - -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)new NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END -<<<<<<< HEAD -#endif -======= - -#endif // AS_USE_PHOTOS ->>>>>>> 565da7d4935740d12fc204aa061faf093831da1e diff --git a/submodules/AsyncDisplayKit/Source/Details/ASPhotosFrameworkImageRequest.m.orig b/submodules/AsyncDisplayKit/Source/Details/ASPhotosFrameworkImageRequest.m.orig deleted file mode 100644 index e24ee882b2..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASPhotosFrameworkImageRequest.m.orig +++ /dev/null @@ -1,163 +0,0 @@ -// -// ASPhotosFrameworkImageRequest.m -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// -#ifndef MINIMAL_ASDK -#import - -#if AS_USE_PHOTOS - -#import - -NSString *const ASPhotosURLScheme = @"ph"; - -static NSString *const _ASPhotosURLQueryKeyWidth = @"width"; -static NSString *const _ASPhotosURLQueryKeyHeight = @"height"; - -// value is PHImageContentMode value -static NSString *const _ASPhotosURLQueryKeyContentMode = @"contentmode"; - -// value is PHImageRequestOptionsResizeMode value -static NSString *const _ASPhotosURLQueryKeyResizeMode = @"resizemode"; - -// value is PHImageRequestOptionsDeliveryMode value -static NSString *const _ASPhotosURLQueryKeyDeliveryMode = @"deliverymode"; - -// value is PHImageRequestOptionsVersion value -static NSString *const _ASPhotosURLQueryKeyVersion = @"version"; - -// value is 0 or 1 -static NSString *const _ASPhotosURLQueryKeyAllowNetworkAccess = @"network"; - -static NSString *const _ASPhotosURLQueryKeyCropOriginX = @"crop_x"; -static NSString *const _ASPhotosURLQueryKeyCropOriginY = @"crop_y"; -static NSString *const _ASPhotosURLQueryKeyCropWidth = @"crop_w"; -static NSString *const _ASPhotosURLQueryKeyCropHeight = @"crop_h"; - -@implementation ASPhotosFrameworkImageRequest - -- (instancetype)initWithAssetIdentifier:(NSString *)assetIdentifier -{ - self = [super init]; - if (self) { - _assetIdentifier = assetIdentifier; - _options = [PHImageRequestOptions new]; - _contentMode = PHImageContentModeDefault; - _targetSize = PHImageManagerMaximumSize; - } - return self; -} - -#pragma mark NSCopying - -- (id)copyWithZone:(NSZone *)zone -{ - ASPhotosFrameworkImageRequest *copy = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:self.assetIdentifier]; - copy.options = [self.options copy]; - copy.targetSize = self.targetSize; - copy.contentMode = self.contentMode; - return copy; -} - -#pragma mark Converting to URL - -- (NSURL *)url -{ - NSURLComponents *comp = [NSURLComponents new]; - comp.scheme = ASPhotosURLScheme; - comp.host = _assetIdentifier; - NSMutableArray *queryItems = [NSMutableArray arrayWithObjects: - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyWidth value:@(_targetSize.width).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyHeight value:@(_targetSize.height).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyVersion value:@(_options.version).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyContentMode value:@(_contentMode).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyAllowNetworkAccess value:@(_options.networkAccessAllowed).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyResizeMode value:@(_options.resizeMode).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyDeliveryMode value:@(_options.deliveryMode).stringValue] - , nil]; - - CGRect cropRect = _options.normalizedCropRect; - if (!CGRectIsEmpty(cropRect)) { - [queryItems addObjectsFromArray:@[ - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropOriginX value:@(cropRect.origin.x).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropOriginY value:@(cropRect.origin.y).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropWidth value:@(cropRect.size.width).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropHeight value:@(cropRect.size.height).stringValue] - ]]; - } - comp.queryItems = queryItems; - return comp.URL; -} - -#pragma mark Converting from URL - -+ (ASPhotosFrameworkImageRequest *)requestWithURL:(NSURL *)url -{ - // not a photos URL - if (![url.scheme isEqualToString:ASPhotosURLScheme]) { - return nil; - } - - NSURLComponents *comp = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; - - ASPhotosFrameworkImageRequest *request = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:url.host]; - - CGRect cropRect = CGRectZero; - CGSize targetSize = PHImageManagerMaximumSize; - for (NSURLQueryItem *item in comp.queryItems) { - if ([_ASPhotosURLQueryKeyAllowNetworkAccess isEqualToString:item.name]) { - request.options.networkAccessAllowed = item.value.boolValue; - } else if ([_ASPhotosURLQueryKeyWidth isEqualToString:item.name]) { - targetSize.width = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyHeight isEqualToString:item.name]) { - targetSize.height = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyContentMode isEqualToString:item.name]) { - request.contentMode = (PHImageContentMode)item.value.integerValue; - } else if ([_ASPhotosURLQueryKeyVersion isEqualToString:item.name]) { - request.options.version = (PHImageRequestOptionsVersion)item.value.integerValue; - } else if ([_ASPhotosURLQueryKeyCropOriginX isEqualToString:item.name]) { - cropRect.origin.x = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyCropOriginY isEqualToString:item.name]) { - cropRect.origin.y = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyCropWidth isEqualToString:item.name]) { - cropRect.size.width = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyCropHeight isEqualToString:item.name]) { - cropRect.size.height = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyResizeMode isEqualToString:item.name]) { - request.options.resizeMode = (PHImageRequestOptionsResizeMode)item.value.integerValue; - } else if ([_ASPhotosURLQueryKeyDeliveryMode isEqualToString:item.name]) { - request.options.deliveryMode = (PHImageRequestOptionsDeliveryMode)item.value.integerValue; - } - } - request.targetSize = targetSize; - request.options.normalizedCropRect = cropRect; - return request; -} - -#pragma mark NSObject - -- (BOOL)isEqual:(id)object -{ - if (![object isKindOfClass:ASPhotosFrameworkImageRequest.class]) { - return NO; - } - ASPhotosFrameworkImageRequest *other = object; - return [other.assetIdentifier isEqualToString:self.assetIdentifier] && - other.contentMode == self.contentMode && - CGSizeEqualToSize(other.targetSize, self.targetSize) && - CGRectEqualToRect(other.options.normalizedCropRect, self.options.normalizedCropRect) && - other.options.resizeMode == self.options.resizeMode && - other.options.version == self.options.version; -} - -@end -<<<<<<< HEAD -#endif -======= - -#endif // AS_USE_PHOTOS ->>>>>>> 565da7d4935740d12fc204aa061faf093831da1e diff --git a/submodules/AsyncDisplayKit/Source/Details/ASPhotosFrameworkImageRequest.mm b/submodules/AsyncDisplayKit/Source/Details/ASPhotosFrameworkImageRequest.mm deleted file mode 100644 index a7e0b41655..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASPhotosFrameworkImageRequest.mm +++ /dev/null @@ -1,159 +0,0 @@ -// -// ASPhotosFrameworkImageRequest.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_USE_PHOTOS - -#import - -NSString *const ASPhotosURLScheme = @"ph"; - -static NSString *const _ASPhotosURLQueryKeyWidth = @"width"; -static NSString *const _ASPhotosURLQueryKeyHeight = @"height"; - -// value is PHImageContentMode value -static NSString *const _ASPhotosURLQueryKeyContentMode = @"contentmode"; - -// value is PHImageRequestOptionsResizeMode value -static NSString *const _ASPhotosURLQueryKeyResizeMode = @"resizemode"; - -// value is PHImageRequestOptionsDeliveryMode value -static NSString *const _ASPhotosURLQueryKeyDeliveryMode = @"deliverymode"; - -// value is PHImageRequestOptionsVersion value -static NSString *const _ASPhotosURLQueryKeyVersion = @"version"; - -// value is 0 or 1 -static NSString *const _ASPhotosURLQueryKeyAllowNetworkAccess = @"network"; - -static NSString *const _ASPhotosURLQueryKeyCropOriginX = @"crop_x"; -static NSString *const _ASPhotosURLQueryKeyCropOriginY = @"crop_y"; -static NSString *const _ASPhotosURLQueryKeyCropWidth = @"crop_w"; -static NSString *const _ASPhotosURLQueryKeyCropHeight = @"crop_h"; - -@implementation ASPhotosFrameworkImageRequest - -- (instancetype)initWithAssetIdentifier:(NSString *)assetIdentifier -{ - self = [super init]; - if (self) { - _assetIdentifier = assetIdentifier; - _options = [PHImageRequestOptions new]; - _contentMode = PHImageContentModeDefault; - _targetSize = PHImageManagerMaximumSize; - } - return self; -} - -#pragma mark NSCopying - -- (id)copyWithZone:(NSZone *)zone -{ - ASPhotosFrameworkImageRequest *copy = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:self.assetIdentifier]; - copy.options = [self.options copy]; - copy.targetSize = self.targetSize; - copy.contentMode = self.contentMode; - return copy; -} - -#pragma mark Converting to URL - -- (NSURL *)url -{ - NSURLComponents *comp = [NSURLComponents new]; - comp.scheme = ASPhotosURLScheme; - comp.host = _assetIdentifier; - NSMutableArray *queryItems = [NSMutableArray arrayWithObjects: - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyWidth value:@(_targetSize.width).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyHeight value:@(_targetSize.height).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyVersion value:@(_options.version).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyContentMode value:@(_contentMode).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyAllowNetworkAccess value:@(_options.networkAccessAllowed).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyResizeMode value:@(_options.resizeMode).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyDeliveryMode value:@(_options.deliveryMode).stringValue] - , nil]; - - CGRect cropRect = _options.normalizedCropRect; - if (!CGRectIsEmpty(cropRect)) { - [queryItems addObjectsFromArray:@[ - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropOriginX value:@(cropRect.origin.x).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropOriginY value:@(cropRect.origin.y).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropWidth value:@(cropRect.size.width).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropHeight value:@(cropRect.size.height).stringValue] - ]]; - } - comp.queryItems = queryItems; - return comp.URL; -} - -#pragma mark Converting from URL - -+ (ASPhotosFrameworkImageRequest *)requestWithURL:(NSURL *)url -{ - // not a photos URL - if (![url.scheme isEqualToString:ASPhotosURLScheme]) { - return nil; - } - - NSURLComponents *comp = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; - - ASPhotosFrameworkImageRequest *request = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:url.host]; - - CGRect cropRect = CGRectZero; - CGSize targetSize = PHImageManagerMaximumSize; - for (NSURLQueryItem *item in comp.queryItems) { - if ([_ASPhotosURLQueryKeyAllowNetworkAccess isEqualToString:item.name]) { - request.options.networkAccessAllowed = item.value.boolValue; - } else if ([_ASPhotosURLQueryKeyWidth isEqualToString:item.name]) { - targetSize.width = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyHeight isEqualToString:item.name]) { - targetSize.height = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyContentMode isEqualToString:item.name]) { - request.contentMode = (PHImageContentMode)item.value.integerValue; - } else if ([_ASPhotosURLQueryKeyVersion isEqualToString:item.name]) { - request.options.version = (PHImageRequestOptionsVersion)item.value.integerValue; - } else if ([_ASPhotosURLQueryKeyCropOriginX isEqualToString:item.name]) { - cropRect.origin.x = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyCropOriginY isEqualToString:item.name]) { - cropRect.origin.y = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyCropWidth isEqualToString:item.name]) { - cropRect.size.width = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyCropHeight isEqualToString:item.name]) { - cropRect.size.height = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyResizeMode isEqualToString:item.name]) { - request.options.resizeMode = (PHImageRequestOptionsResizeMode)item.value.integerValue; - } else if ([_ASPhotosURLQueryKeyDeliveryMode isEqualToString:item.name]) { - request.options.deliveryMode = (PHImageRequestOptionsDeliveryMode)item.value.integerValue; - } - } - request.targetSize = targetSize; - request.options.normalizedCropRect = cropRect; - return request; -} - -#pragma mark NSObject - -- (BOOL)isEqual:(id)object -{ - if (![object isKindOfClass:ASPhotosFrameworkImageRequest.class]) { - return NO; - } - ASPhotosFrameworkImageRequest *other = object; - return [other.assetIdentifier isEqualToString:self.assetIdentifier] && - other.contentMode == self.contentMode && - CGSizeEqualToSize(other.targetSize, self.targetSize) && - CGRectEqualToRect(other.options.normalizedCropRect, self.options.normalizedCropRect) && - other.options.resizeMode == self.options.resizeMode && - other.options.version == self.options.version; -} - -@end - -#endif // AS_USE_PHOTOS diff --git a/submodules/AsyncDisplayKit/Source/Details/ASRangeController.h b/submodules/AsyncDisplayKit/Source/Details/ASRangeController.h deleted file mode 100644 index ebdc9befaf..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASRangeController.h +++ /dev/null @@ -1,206 +0,0 @@ -// -// ASRangeController.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import -#import -#import -#import -#import -#import - -#define ASRangeControllerLoggingEnabled 0 - -NS_ASSUME_NONNULL_BEGIN - -@class _ASHierarchyChangeSet; -@protocol ASRangeControllerDataSource; -@protocol ASRangeControllerDelegate; -@protocol ASLayoutController; - -/** - * Working range controller. - * - * Used internally by ASTableView and ASCollectionView. It is paired with ASDataController. - * It is designed to support custom scrolling containers as well. Observes the visible range, maintains - * "working ranges" to trigger network calls and rendering, and is responsible for driving asynchronous layout of cells. - * This includes cancelling those asynchronous operations as cells fall outside of the working ranges. - */ -AS_SUBCLASSING_RESTRICTED -@interface ASRangeController : NSObject -{ - id _layoutController; - __weak id _dataSource; - __weak id _delegate; -} - -/** - * Notify the range controller that the visible range has been updated. - * This is the primary input call that drives updating the working ranges, and triggering their actions. - * The ranges will be updated in the next turn of the main loop, or when -updateIfNeeded is called. - * - * @see [ASRangeControllerDelegate rangeControllerVisibleNodeIndexPaths:] - */ -- (void)setNeedsUpdate; - -/** - * Update the ranges immediately, if -setNeedsUpdate has been called since the last update. - * This is useful because the ranges must be updated immediately after a cell is added - * into a table/collection to satisfy interface state API guarantees. - */ -- (void)updateIfNeeded; - -/** - * Force update the ranges immediately. - */ -- (void)updateRanges; - -/** - * Add the sized node for `indexPath` as a subview of `contentView`. - * - * @param contentView UIView to add a (sized) node's view to. - * - * @param node The cell node to be added. - */ -- (void)configureContentView:(UIView *)contentView forCellNode:(ASCellNode *)node; - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; - -- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; - -// These methods call the corresponding method on each node, visiting each one that -// the range controller has set a non-default interface state on. -- (void)clearContents; -- (void)clearPreloadedData; - -/** - * An object that describes the layout behavior of the ranged component (table view, collection view, etc.) - * - * Used primarily for providing the current range of index paths and identifying when the - * range controller should invalidate its range. - */ -@property (nonatomic) id layoutController; - -/** - * The underlying data source for the range controller - */ -@property (nonatomic, weak) id dataSource; - -/** - * Delegate for handling range controller events. Must not be nil. - */ -@property (nonatomic, weak) id delegate; - -/** - * Property that indicates whether the scroll view for this range controller has ever changed its contentOffset. - */ -@property (nonatomic) BOOL contentHasBeenScrolled; - -@end - - -/** - * Data source for ASRangeController. - * - * Allows the range controller to perform external queries on the range. - * Ex. range nodes, visible index paths, and viewport size. - */ -@protocol ASRangeControllerDataSource - -/** - * @param rangeController Sender. - * - * @return an table of elements corresponding to the data currently visible onscreen (i.e., the visible range). - */ -- (nullable NSHashTable *)visibleElementsForRangeController:(ASRangeController *)rangeController; - -/** - * @param rangeController Sender. - * - * @return the current scroll direction of the view using this range controller. - */ -- (ASScrollDirection)scrollDirectionForRangeController:(ASRangeController *)rangeController; - -/** - * @param rangeController Sender. - * - * @return the ASInterfaceState of the node that this controller is powering. This allows nested range controllers - * to collaborate with one another, as an outer controller may set bits in .interfaceState such as Visible. - * If this controller is an orthogonally scrolling element, it waits until it is visible to preload outside the viewport. - */ -- (ASInterfaceState)interfaceStateForRangeController:(ASRangeController *)rangeController; - -- (ASElementMap *)elementMapForRangeController:(ASRangeController *)rangeController; - -- (NSString *)nameForRangeControllerDataSource; - -@end - -/** - * Delegate for ASRangeController. - */ -@protocol ASRangeControllerDelegate - -/** - * Called to update with given change set. - * - * @param changeSet The change set that includes all updates - * - * @param updates The block that performs relevant data updates. - * - * @discussion The updates block must always be executed or the data controller will get into a bad state. - * It should be called at the time the backing view is ready to process the updates, - * i.e inside the updates block of `-[UICollectionView performBatchUpdates:completion:] or after calling `-[UITableView beginUpdates]`. - */ -- (void)rangeController:(ASRangeController *)rangeController updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet updates:(dispatch_block_t)updates; - -- (BOOL)rangeControllerShouldUpdateRanges:(ASRangeController *)rangeController; - -@end - -@interface ASRangeController (ASRangeControllerUpdateRangeProtocol) - -/** - * Update the range mode for a range controller to a explicitly set mode until the node that contains the range - * controller becomes visible again - * - * Logic for the automatic range mode: - * 1. If there are no visible node paths available nothing is to be done and no range update will happen - * 2. The initial range update if the range controller is visible always will be - * ASLayoutRangeModeMinimum as it's the initial fetch - * 3. The range mode set explicitly via updateCurrentRangeWithMode: will last at least one range update. After that it - the range controller will use the explicit set range mode until it becomes visible and a new range update was - triggered or a new range mode via updateCurrentRangeWithMode: is set - * 4. If range mode is not explicitly set the range mode is variying based if the range controller is visible or not - */ -- (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode; - -@end - -@interface ASRangeController (DebugInternal) - -+ (void)layoutDebugOverlayIfNeeded; - -- (void)addRangeControllerToRangeDebugOverlay; - -- (void)updateRangeController:(ASRangeController *)controller - withScrollableDirections:(ASScrollDirection)scrollableDirections - scrollDirection:(ASScrollDirection)direction - rangeMode:(ASLayoutRangeMode)mode - displayTuningParameters:(ASRangeTuningParameters)displayTuningParameters - preloadTuningParameters:(ASRangeTuningParameters)preloadTuningParameters - interfaceState:(ASInterfaceState)interfaceState; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASRangeController.mm b/submodules/AsyncDisplayKit/Source/Details/ASRangeController.mm deleted file mode 100644 index f3539ea4b7..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASRangeController.mm +++ /dev/null @@ -1,673 +0,0 @@ -// -// ASRangeController.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK -#import - -#import -#import -#import -#import -#import -#import // Required for interfaceState and hierarchyState setter methods. -#import -#import -#import -#import -#import - -#import -#import - -#define AS_RANGECONTROLLER_LOG_UPDATE_FREQ 0 - -#ifndef ASRangeControllerAutomaticLowMemoryHandling -#define ASRangeControllerAutomaticLowMemoryHandling 1 -#endif - -@interface ASRangeController () -{ - BOOL _rangeIsValid; - BOOL _needsRangeUpdate; - NSSet *_allPreviousIndexPaths; - NSHashTable *_visibleNodes; - ASLayoutRangeMode _currentRangeMode; - BOOL _contentHasBeenScrolled; - BOOL _preserveCurrentRangeMode; - BOOL _didRegisterForNodeDisplayNotifications; - CFTimeInterval _pendingDisplayNodesTimestamp; - - // If the user is not currently scrolling, we will keep our ranges - // configured to match their previous scroll direction. Defaults - // to [.right, .down] so that when the user first opens a screen - // the ranges point down into the content. - ASScrollDirection _previousScrollDirection; - -#if AS_RANGECONTROLLER_LOG_UPDATE_FREQ - NSUInteger _updateCountThisFrame; - CADisplayLink *_displayLink; -#endif -} - -@end - -static UIApplicationState __ApplicationState = UIApplicationStateActive; - -@implementation ASRangeController - -#pragma mark - Lifecycle - -- (instancetype)init -{ - if (!(self = [super init])) { - return nil; - } - - _rangeIsValid = YES; - _currentRangeMode = ASLayoutRangeModeUnspecified; - _contentHasBeenScrolled = NO; - _preserveCurrentRangeMode = NO; - _previousScrollDirection = ASScrollDirectionDown | ASScrollDirectionRight; - - [[[self class] allRangeControllersWeakSet] addObject:self]; - -#if AS_RANGECONTROLLER_LOG_UPDATE_FREQ - _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_updateCountDisplayLinkDidFire)]; - [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; -#endif - - if (ASDisplayNode.shouldShowRangeDebugOverlay) { - [self addRangeControllerToRangeDebugOverlay]; - } - - return self; -} - -- (void)dealloc -{ -#if AS_RANGECONTROLLER_LOG_UPDATE_FREQ - [_displayLink invalidate]; -#endif - - if (_didRegisterForNodeDisplayNotifications) { - [[NSNotificationCenter defaultCenter] removeObserver:self name:ASRenderingEngineDidDisplayScheduledNodesNotification object:nil]; - } -} - -#pragma mark - Core visible node range management API - -+ (BOOL)isFirstRangeUpdateForRangeMode:(ASLayoutRangeMode)rangeMode -{ - return (rangeMode == ASLayoutRangeModeUnspecified); -} - -+ (ASLayoutRangeMode)rangeModeForInterfaceState:(ASInterfaceState)interfaceState - currentRangeMode:(ASLayoutRangeMode)currentRangeMode -{ - BOOL isVisible = (ASInterfaceStateIncludesVisible(interfaceState)); - BOOL isFirstRangeUpdate = [self isFirstRangeUpdateForRangeMode:currentRangeMode]; - if (!isVisible || isFirstRangeUpdate) { - return ASLayoutRangeModeMinimum; - } - - return ASLayoutRangeModeFull; -} - -- (ASInterfaceState)interfaceState -{ - ASInterfaceState selfInterfaceState = ASInterfaceStateNone; - if (_dataSource) { - selfInterfaceState = [_dataSource interfaceStateForRangeController:self]; - } - if (__ApplicationState == UIApplicationStateBackground) { - // If the app is background, pretend to be invisible so that we inform each cell it is no longer being viewed by the user - selfInterfaceState &= ~(ASInterfaceStateVisible); - } - return selfInterfaceState; -} - -- (void)setNeedsUpdate -{ - if (!_needsRangeUpdate) { - _needsRangeUpdate = YES; - - __weak __typeof__(self) weakSelf = self; - dispatch_async(dispatch_get_main_queue(), ^{ - [weakSelf updateIfNeeded]; - }); - } -} - -- (void)updateIfNeeded -{ - if (_needsRangeUpdate) { - [self updateRanges]; - } -} - -- (void)updateRanges -{ - _needsRangeUpdate = NO; - [self _updateVisibleNodeIndexPaths]; -} - -- (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode -{ - _preserveCurrentRangeMode = YES; - if (_currentRangeMode != rangeMode) { - _currentRangeMode = rangeMode; - - [self setNeedsUpdate]; - } -} - -- (void)setLayoutController:(id)layoutController -{ - _layoutController = layoutController; - if (layoutController && _dataSource) { - [self updateIfNeeded]; - } -} - -- (void)setDataSource:(id)dataSource -{ - _dataSource = dataSource; - if (dataSource && _layoutController) { - [self updateIfNeeded]; - } -} - -// Clear the visible bit from any nodes that disappeared since last update. -// Currently we guarantee that nodes will not be marked visible when deallocated, -// but it's OK to be in e.g. the preload range. So for the visible bit specifically, -// we add this extra mechanism to account for e.g. deleted items. -// -// NOTE: There is a minor risk here, if a node is transferred from one range controller -// to another before the first rc updates and clears the node out of this set. It's a pretty -// wild scenario that I doubt happens in practice. -- (void)_setVisibleNodes:(NSHashTable *)newVisibleNodes -{ - for (ASCellNode *node in _visibleNodes) { - if (![newVisibleNodes containsObject:node] && node.isVisible) { - [node exitInterfaceState:ASInterfaceStateVisible]; - } - } - _visibleNodes = newVisibleNodes; -} - -- (void)_updateVisibleNodeIndexPaths -{ - as_activity_scope_verbose(as_activity_create("Update range controller", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT)); - as_log_verbose(ASCollectionLog(), "Updating ranges for %@", ASViewToDisplayNode(ASDynamicCast(self.delegate, UIView))); - ASDisplayNodeAssert(_layoutController, @"An ASLayoutController is required by ASRangeController"); - if (!_layoutController || !_dataSource) { - return; - } - - if (![_delegate rangeControllerShouldUpdateRanges:self]) { - return; - } - -#if AS_RANGECONTROLLER_LOG_UPDATE_FREQ - _updateCountThisFrame += 1; -#endif - - ASElementMap *map = [_dataSource elementMapForRangeController:self]; - - // TODO: Consider if we need to use this codepath, or can rely on something more similar to the data & display ranges - // Example: ... = [_layoutController indexPathsForScrolling:scrollDirection rangeType:ASLayoutRangeTypeVisible]; - auto visibleElements = [_dataSource visibleElementsForRangeController:self]; - NSHashTable *newVisibleNodes = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality]; - - ASSignpostStart(ASSignpostRangeControllerUpdate); - - // Get the scroll direction. Default to using the previous one, if they're not scrolling. - ASScrollDirection scrollDirection = [_dataSource scrollDirectionForRangeController:self]; - if (scrollDirection == ASScrollDirectionNone) { - scrollDirection = _previousScrollDirection; - } - _previousScrollDirection = scrollDirection; - - if (visibleElements.count == 0) { // if we don't have any visibleNodes currently (scrolled before or after content)... - // Verify the actual state by checking the layout with a "VisibleOnly" range. - // This allows us to avoid thrashing through -didExitVisibleState in the case of -reloadData, since that generates didEndDisplayingCell calls. - // Those didEndDisplayingCell calls result in items being removed from the visibleElements returned by the _dataSource, even though the layout remains correct. - visibleElements = [_layoutController elementsForScrolling:scrollDirection rangeMode:ASLayoutRangeModeVisibleOnly rangeType:ASLayoutRangeTypeDisplay map:map]; - for (ASCollectionElement *element in visibleElements) { - [newVisibleNodes addObject:element.node]; - } - [self _setVisibleNodes:newVisibleNodes]; - return; // don't do anything for this update, but leave _rangeIsValid == NO to make sure we update it later - } - - ASInterfaceState selfInterfaceState = [self interfaceState]; - ASLayoutRangeMode rangeMode = _currentRangeMode; - BOOL updateRangeMode = (!_preserveCurrentRangeMode && _contentHasBeenScrolled); - - // If we've never scrolled before, we never update the range mode, so it doesn't jump into Full too early. - // This can happen if we have multiple, noisy updates occurring from application code before the user has engaged. - // If the range mode is explicitly set via updateCurrentRangeWithMode:, we'll preserve that for at least one update cycle. - // Once the user has scrolled and the range is visible, we'll always resume managing the range mode automatically. - if ((updateRangeMode && ASInterfaceStateIncludesVisible(selfInterfaceState)) || [[self class] isFirstRangeUpdateForRangeMode:rangeMode]) { - rangeMode = [ASRangeController rangeModeForInterfaceState:selfInterfaceState currentRangeMode:_currentRangeMode]; - } - - ASRangeTuningParameters parametersPreload = [_layoutController tuningParametersForRangeMode:rangeMode - rangeType:ASLayoutRangeTypePreload]; - ASRangeTuningParameters parametersDisplay = [_layoutController tuningParametersForRangeMode:rangeMode - rangeType:ASLayoutRangeTypeDisplay]; - - // Preload can express the ultra-low-memory state with 0, 0 returned for its tuningParameters above, and will match Visible. - // However, in this rangeMode, Display is not supposed to contain *any* paths -- not even the visible bounds. TuningParameters can't express this. - BOOL emptyDisplayRange = (rangeMode == ASLayoutRangeModeLowMemory); - BOOL equalDisplayPreload = ASRangeTuningParametersEqualToRangeTuningParameters(parametersDisplay, parametersPreload); - BOOL equalDisplayVisible = (ASRangeTuningParametersEqualToRangeTuningParameters(parametersDisplay, ASRangeTuningParametersZero) - && emptyDisplayRange == NO); - - // Check if both Display and Preload are unique. If they are, we load them with a single fetch from the layout controller for performance. - BOOL optimizedLoadingOfBothRanges = (equalDisplayPreload == NO && equalDisplayVisible == NO && emptyDisplayRange == NO); - - NSHashTable *displayElements = nil; - NSHashTable *preloadElements = nil; - - if (optimizedLoadingOfBothRanges) { - [_layoutController allElementsForScrolling:scrollDirection rangeMode:rangeMode displaySet:&displayElements preloadSet:&preloadElements map:map]; - } else { - if (emptyDisplayRange == YES) { - displayElements = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality]; - } else if (equalDisplayVisible == YES) { - displayElements = visibleElements; - } else { - // Calculating only the Display range means the Preload range is either the same as Display or Visible. - displayElements = [_layoutController elementsForScrolling:scrollDirection rangeMode:rangeMode rangeType:ASLayoutRangeTypeDisplay map:map]; - } - - BOOL equalPreloadVisible = ASRangeTuningParametersEqualToRangeTuningParameters(parametersPreload, ASRangeTuningParametersZero); - if (equalDisplayPreload == YES) { - preloadElements = displayElements; - } else if (equalPreloadVisible == YES) { - preloadElements = visibleElements; - } else { - preloadElements = [_layoutController elementsForScrolling:scrollDirection rangeMode:rangeMode rangeType:ASLayoutRangeTypePreload map:map]; - } - } - - // For now we are only interested in items. Filter-map out from element to item-index-path. - NSSet *visibleIndexPaths = ASSetByFlatMapping(visibleElements, ASCollectionElement *element, [map indexPathForElementIfCell:element]); - NSSet *displayIndexPaths = ASSetByFlatMapping(displayElements, ASCollectionElement *element, [map indexPathForElementIfCell:element]); - NSSet *preloadIndexPaths = ASSetByFlatMapping(preloadElements, ASCollectionElement *element, [map indexPathForElementIfCell:element]); - - // Prioritize the order in which we visit each. Visible nodes should be updated first so they are enqueued on - // the network or display queues before preloading (offscreen) nodes are enqueued. - NSMutableOrderedSet *allIndexPaths = [[NSMutableOrderedSet alloc] initWithSet:visibleIndexPaths]; - - // Typically the preloadIndexPaths will be the largest, and be a superset of the others, though it may be disjoint. - // Because allIndexPaths is an NSMutableOrderedSet, this adds the non-duplicate items /after/ the existing items. - // This means that during iteration, we will first visit visible, then display, then preload nodes. - [allIndexPaths unionSet:displayIndexPaths]; - [allIndexPaths unionSet:preloadIndexPaths]; - - // Add anything we had applied interfaceState to in the last update, but is no longer in range, so we can clear any - // range flags it still has enabled. Most of the time, all but a few elements are equal; a large programmatic - // scroll or major main thread stall could cause entirely disjoint sets. In either case we must visit all. - // Calling "-set" on NSMutableOrderedSet just references the underlying mutable data store, so we must copy it. - NSSet *allCurrentIndexPaths = [[allIndexPaths set] copy]; - [allIndexPaths unionSet:_allPreviousIndexPaths]; - _allPreviousIndexPaths = allCurrentIndexPaths; - - _currentRangeMode = rangeMode; - _preserveCurrentRangeMode = NO; - - if (!_rangeIsValid) { - [allIndexPaths addObjectsFromArray:map.itemIndexPaths]; - } - -#if ASRangeControllerLoggingEnabled - ASDisplayNodeAssertTrue([visibleIndexPaths isSubsetOfSet:displayIndexPaths]); - NSMutableArray *modifiedIndexPaths = (ASRangeControllerLoggingEnabled ? [NSMutableArray array] : nil); -#endif - - for (NSIndexPath *indexPath in allIndexPaths) { - // Before a node / indexPath is exposed to ASRangeController, ASDataController should have already measured it. - // For consistency, make sure each node knows that it should measure itself if something changes. - ASInterfaceState interfaceState = ASInterfaceStateMeasureLayout; - - if (ASInterfaceStateIncludesVisible(selfInterfaceState)) { - if ([visibleIndexPaths containsObject:indexPath]) { - interfaceState |= (ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStatePreload); - } else { - if ([preloadIndexPaths containsObject:indexPath]) { - interfaceState |= ASInterfaceStatePreload; - } - if ([displayIndexPaths containsObject:indexPath]) { - interfaceState |= ASInterfaceStateDisplay; - } - } - } else { - // If selfInterfaceState isn't visible, then visibleIndexPaths represents what /will/ be immediately visible at the - // instant we come onscreen. So, preload and display all of those things, but don't waste resources preloading yet. - // We handle this as a separate case to minimize set operations for offscreen preloading, including containsObject:. - - if ([allCurrentIndexPaths containsObject:indexPath]) { - // DO NOT set Visible: even though these elements are in the visible range / "viewport", - // our overall container object is itself not visible yet. The moment it becomes visible, we will run the condition above - - // Set Layout, Preload - interfaceState |= ASInterfaceStatePreload; - - if (rangeMode != ASLayoutRangeModeLowMemory) { - // Add Display. - // We might be looking at an indexPath that was previously in-range, but now we need to clear it. - // In that case we'll just set it back to MeasureLayout. Only set Display | Preload if in allCurrentIndexPaths. - interfaceState |= ASInterfaceStateDisplay; - } - } - } - - ASCellNode *node = [map elementForItemAtIndexPath:indexPath].nodeIfAllocated; - if (node != nil) { - ASDisplayNodeAssert(node.hierarchyState & ASHierarchyStateRangeManaged, @"All nodes reaching this point should be range-managed, or interfaceState may be incorrectly reset."); - if (ASInterfaceStateIncludesVisible(interfaceState)) { - [newVisibleNodes addObject:node]; - } - // Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState. - if (node.pendingInterfaceState != interfaceState) { -#if ASRangeControllerLoggingEnabled - [modifiedIndexPaths addObject:indexPath]; -#endif - - BOOL nodeShouldScheduleDisplay = [node shouldScheduleDisplayWithNewInterfaceState:interfaceState]; - [node recursivelySetInterfaceState:interfaceState]; - - if (nodeShouldScheduleDisplay) { - [self registerForNodeDisplayNotificationsForInterfaceStateIfNeeded:selfInterfaceState]; - if (_didRegisterForNodeDisplayNotifications) { - _pendingDisplayNodesTimestamp = CACurrentMediaTime(); - } - } - } - } - } - - [self _setVisibleNodes:newVisibleNodes]; - - // TODO: This code is for debugging only, but would be great to clean up with a delegate method implementation. - if (ASDisplayNode.shouldShowRangeDebugOverlay) { - ASScrollDirection scrollableDirections = ASScrollDirectionUp | ASScrollDirectionDown; - if ([_dataSource isKindOfClass:NSClassFromString(@"ASCollectionView")]) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wundeclared-selector" - scrollableDirections = (ASScrollDirection)[_dataSource performSelector:@selector(scrollableDirections)]; -#pragma clang diagnostic pop - } - - [self updateRangeController:self - withScrollableDirections:scrollableDirections - scrollDirection:scrollDirection - rangeMode:rangeMode - displayTuningParameters:parametersDisplay - preloadTuningParameters:parametersPreload - interfaceState:selfInterfaceState]; - } - - _rangeIsValid = YES; - -#if ASRangeControllerLoggingEnabled -// NSSet *visibleNodePathsSet = [NSSet setWithArray:visibleNodePaths]; -// BOOL setsAreEqual = [visibleIndexPaths isEqualToSet:visibleNodePathsSet]; -// NSLog(@"visible sets are equal: %d", setsAreEqual); -// if (!setsAreEqual) { -// NSLog(@"standard: %@", visibleIndexPaths); -// NSLog(@"custom: %@", visibleNodePathsSet); -// } - [modifiedIndexPaths sortUsingSelector:@selector(compare:)]; - NSLog(@"Range update complete; modifiedIndexPaths: %@, rangeMode: %d", [self descriptionWithIndexPaths:modifiedIndexPaths], rangeMode); -#endif - - ASSignpostEnd(ASSignpostRangeControllerUpdate); -} - -#pragma mark - Notification observers - -/** - * If we're in a restricted range mode, but we're going to change to a full range mode soon, - * go ahead and schedule the transition as soon as all the currently-scheduled rendering is done #1163. - */ -- (void)registerForNodeDisplayNotificationsForInterfaceStateIfNeeded:(ASInterfaceState)interfaceState -{ - // Do not schedule to listen if we're already in full range mode. - // This avoids updating the range controller during a collection teardown when it is removed - // from the hierarchy and its data source is cleared, causing UIKit to call -reloadData. - if (!_didRegisterForNodeDisplayNotifications && _currentRangeMode != ASLayoutRangeModeFull) { - ASLayoutRangeMode nextRangeMode = [ASRangeController rangeModeForInterfaceState:interfaceState - currentRangeMode:_currentRangeMode]; - if (_currentRangeMode != nextRangeMode) { - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(scheduledNodesDidDisplay:) - name:ASRenderingEngineDidDisplayScheduledNodesNotification - object:nil]; - _didRegisterForNodeDisplayNotifications = YES; - } - } -} - -- (void)scheduledNodesDidDisplay:(NSNotification *)notification -{ - CFAbsoluteTime notificationTimestamp = ((NSNumber *) notification.userInfo[ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp]).doubleValue; - if (_pendingDisplayNodesTimestamp < notificationTimestamp) { - // The rendering engine has processed all the nodes this range controller scheduled. Let's schedule a range update - [[NSNotificationCenter defaultCenter] removeObserver:self name:ASRenderingEngineDidDisplayScheduledNodesNotification object:nil]; - _didRegisterForNodeDisplayNotifications = NO; - - [self setNeedsUpdate]; - } -} - -#pragma mark - Cell node view handling - -- (void)configureContentView:(UIView *)contentView forCellNode:(ASCellNode *)node -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(node, @"Cannot move a nil node to a view"); - ASDisplayNodeAssert(contentView, @"Cannot move a node to a non-existent view"); - - if (node.shouldUseUIKitCell) { - // When using UIKit cells, the ASCellNode is just a placeholder object with a preferredSize. - // In this case, we should not disrupt the subviews of the contentView. - return; - } - - if (node.view.superview == contentView) { - // this content view is already correctly configured - return; - } - - // clean the content view - for (UIView *view in contentView.subviews) { - [view removeFromSuperview]; - } - - [contentView addSubview:node.view]; -} - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - [_layoutController setTuningParameters:tuningParameters forRangeMode:rangeMode rangeType:rangeType]; -} - -- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - return [_layoutController tuningParametersForRangeMode:rangeMode rangeType:rangeType]; -} - -#pragma mark - ASDataControllerDelegete - -- (void)dataController:(ASDataController *)dataController updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet updates:(dispatch_block_t)updates -{ - ASDisplayNodeAssertMainThread(); - if (changeSet.includesReloadData) { - [self _setVisibleNodes:nil]; - } - _rangeIsValid = NO; - [_delegate rangeController:self updateWithChangeSet:changeSet updates:updates]; -} - -#pragma mark - Memory Management - -// Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState. -- (void)clearContents -{ - ASDisplayNodeAssertMainThread(); - for (ASCollectionElement *element in [_dataSource elementMapForRangeController:self]) { - ASCellNode *node = element.nodeIfAllocated; - if (ASInterfaceStateIncludesDisplay(node.interfaceState)) { - [node exitInterfaceState:ASInterfaceStateDisplay]; - } - } -} - -- (void)clearPreloadedData -{ - ASDisplayNodeAssertMainThread(); - for (ASCollectionElement *element in [_dataSource elementMapForRangeController:self]) { - ASCellNode *node = element.nodeIfAllocated; - if (ASInterfaceStateIncludesPreload(node.interfaceState)) { - [node exitInterfaceState:ASInterfaceStatePreload]; - } - } -} - -#pragma mark - Class Methods (Application Notification Handlers) - -+ (ASWeakSet *)allRangeControllersWeakSet -{ - static ASWeakSet *__allRangeControllersWeakSet; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - __allRangeControllersWeakSet = [[ASWeakSet alloc] init]; - [self registerSharedApplicationNotifications]; - }); - return __allRangeControllersWeakSet; -} - -+ (void)registerSharedApplicationNotifications -{ - NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; -#if ASRangeControllerAutomaticLowMemoryHandling - [center addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; -#endif - [center addObserver:self selector:@selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; - [center addObserver:self selector:@selector(willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; -} - -static ASLayoutRangeMode __rangeModeForMemoryWarnings = ASLayoutRangeModeLowMemory; -+ (void)setRangeModeForMemoryWarnings:(ASLayoutRangeMode)rangeMode -{ - ASDisplayNodeAssert(rangeMode == ASLayoutRangeModeVisibleOnly || rangeMode == ASLayoutRangeModeLowMemory, @"It is highly inadvisable to engage a larger range mode when a memory warning occurs, as this will almost certainly cause app eviction"); - __rangeModeForMemoryWarnings = rangeMode; -} - -+ (void)didReceiveMemoryWarning:(NSNotification *)notification -{ - NSArray *allRangeControllers = [[self allRangeControllersWeakSet] allObjects]; - for (ASRangeController *rangeController in allRangeControllers) { - BOOL isDisplay = ASInterfaceStateIncludesDisplay([rangeController interfaceState]); - [rangeController updateCurrentRangeWithMode:isDisplay ? ASLayoutRangeModeVisibleOnly : __rangeModeForMemoryWarnings]; - // There's no need to call needs update as updateCurrentRangeWithMode sets this if necessary. - [rangeController updateIfNeeded]; - } - -#if ASRangeControllerLoggingEnabled - NSLog(@"+[ASRangeController didReceiveMemoryWarning] with controllers: %@", allRangeControllers); -#endif -} - -+ (void)didEnterBackground:(NSNotification *)notification -{ - NSArray *allRangeControllers = [[self allRangeControllersWeakSet] allObjects]; - for (ASRangeController *rangeController in allRangeControllers) { - // We do not want to fully collapse the Display ranges of any visible range controllers so that flashes can be avoided when - // the app is resumed. Non-visible controllers can be more aggressively culled to the LowMemory state (see definitions for documentation) - BOOL isVisible = ASInterfaceStateIncludesVisible([rangeController interfaceState]); - [rangeController updateCurrentRangeWithMode:isVisible ? ASLayoutRangeModeVisibleOnly : ASLayoutRangeModeLowMemory]; - } - - // Because -interfaceState checks __ApplicationState and always clears the "visible" bit if Backgrounded, we must set this after updating the range mode. - __ApplicationState = UIApplicationStateBackground; - for (ASRangeController *rangeController in allRangeControllers) { - // Trigger a range update immediately, as we may not be allowed by the system to run the update block scheduled by changing range mode. - // There's no need to call needs update as updateCurrentRangeWithMode sets this if necessary. - [rangeController updateIfNeeded]; - } - -#if ASRangeControllerLoggingEnabled - NSLog(@"+[ASRangeController didEnterBackground] with controllers, after backgrounding: %@", allRangeControllers); -#endif -} - -+ (void)willEnterForeground:(NSNotification *)notification -{ - NSArray *allRangeControllers = [[self allRangeControllersWeakSet] allObjects]; - __ApplicationState = UIApplicationStateActive; - for (ASRangeController *rangeController in allRangeControllers) { - BOOL isVisible = ASInterfaceStateIncludesVisible([rangeController interfaceState]); - [rangeController updateCurrentRangeWithMode:isVisible ? ASLayoutRangeModeMinimum : ASLayoutRangeModeVisibleOnly]; - // There's no need to call needs update as updateCurrentRangeWithMode sets this if necessary. - [rangeController updateIfNeeded]; - } - -#if ASRangeControllerLoggingEnabled - NSLog(@"+[ASRangeController willEnterForeground] with controllers, after foregrounding: %@", allRangeControllers); -#endif -} - -#pragma mark - Debugging - -#if AS_RANGECONTROLLER_LOG_UPDATE_FREQ -- (void)_updateCountDisplayLinkDidFire -{ - if (_updateCountThisFrame > 1) { - NSLog(@"ASRangeController %p updated %lu times this frame.", self, (unsigned long)_updateCountThisFrame); - } - _updateCountThisFrame = 0; -} -#endif - -- (NSString *)descriptionWithIndexPaths:(NSArray *)indexPaths -{ - NSMutableString *description = [NSMutableString stringWithFormat:@"%@ %@", [super description], @" allPreviousIndexPaths:\n"]; - for (NSIndexPath *indexPath in indexPaths) { - ASDisplayNode *node = [[_dataSource elementMapForRangeController:self] elementForItemAtIndexPath:indexPath].nodeIfAllocated; - ASInterfaceState interfaceState = node.interfaceState; - BOOL inVisible = ASInterfaceStateIncludesVisible(interfaceState); - BOOL inDisplay = ASInterfaceStateIncludesDisplay(interfaceState); - BOOL inPreload = ASInterfaceStateIncludesPreload(interfaceState); - [description appendFormat:@"indexPath %@, Visible: %d, Display: %d, Preload: %d\n", indexPath, inVisible, inDisplay, inPreload]; - } - return description; -} - -- (NSString *)description -{ - NSArray *indexPaths = [[_allPreviousIndexPaths allObjects] sortedArrayUsingSelector:@selector(compare:)]; - return [self descriptionWithIndexPaths:indexPaths]; -} - -@end - -@implementation ASDisplayNode (RangeModeConfiguring) - -+ (void)setRangeModeForMemoryWarnings:(ASLayoutRangeMode)rangeMode -{ - [ASRangeController setRangeModeForMemoryWarnings:rangeMode]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASRangeControllerUpdateRangeProtocol+Beta.h b/submodules/AsyncDisplayKit/Source/Details/ASRangeControllerUpdateRangeProtocol+Beta.h deleted file mode 100644 index 5bc4fae301..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASRangeControllerUpdateRangeProtocol+Beta.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// ASRangeControllerUpdateRangeProtocol+Beta.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK -#import -#import - -@protocol ASRangeControllerUpdateRangeProtocol - -/** - * Updates the current range mode of the range controller for at least the next range update - * and, if the new mode is different from the previous mode, enqueues a range update. - */ -- (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode; - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASSectionContext.h b/submodules/AsyncDisplayKit/Source/Details/ASSectionContext.h deleted file mode 100644 index e7d4a190a4..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASSectionContext.h +++ /dev/null @@ -1,25 +0,0 @@ -// -// ASSectionContext.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK -#import - -@class ASCollectionView; - -@protocol ASSectionContext - -/** - * Custom name of this section, for debugging only. - */ -@property (nonatomic, copy, nullable) NSString *sectionName; -@property (nonatomic, weak, nullable) ASCollectionView *collectionView; - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASTableLayoutController.h b/submodules/AsyncDisplayKit/Source/Details/ASTableLayoutController.h deleted file mode 100644 index c294c2ecb4..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASTableLayoutController.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// ASTableLayoutController.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@class UITableView; - -/** - * A layout controller designed for use with UITableView. - */ -AS_SUBCLASSING_RESTRICTED -@interface ASTableLayoutController : ASAbstractLayoutController - -@property (nonatomic, weak, readonly) UITableView *tableView; - -- (instancetype)initWithTableView:(UITableView *)tableView; - -@end - -NS_ASSUME_NONNULL_END -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASTableLayoutController.mm b/submodules/AsyncDisplayKit/Source/Details/ASTableLayoutController.mm deleted file mode 100644 index 0669c76131..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASTableLayoutController.mm +++ /dev/null @@ -1,55 +0,0 @@ -// -// ASTableLayoutController.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// -#ifndef MINIMAL_ASDK -#import - -#import - -#import -#import - -@interface ASTableLayoutController() -@end - -@implementation ASTableLayoutController - -- (instancetype)initWithTableView:(UITableView *)tableView -{ - if (!(self = [super init])) { - return nil; - } - _tableView = tableView; - return self; -} - -#pragma mark - ASLayoutController - -- (NSHashTable *)elementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType map:(ASElementMap *)map -{ - CGRect bounds = _tableView.bounds; - - ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeMode:rangeMode rangeType:rangeType]; - CGRect rangeBounds = CGRectExpandToRangeWithScrollableDirections(bounds, tuningParameters, ASScrollDirectionVerticalDirections, scrollDirection); - NSArray *array = [_tableView indexPathsForRowsInRect:rangeBounds]; - return ASPointerTableByFlatMapping(array, NSIndexPath *indexPath, [map elementForItemAtIndexPath:indexPath]); -} - -- (void)allElementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode displaySet:(NSHashTable *__autoreleasing _Nullable *)displaySet preloadSet:(NSHashTable *__autoreleasing _Nullable *)preloadSet map:(ASElementMap *)map -{ - if (displaySet == NULL || preloadSet == NULL) { - return; - } - - *displaySet = [self elementsForScrolling:scrollDirection rangeMode:rangeMode rangeType:ASLayoutRangeTypeDisplay map:map]; - *preloadSet = [self elementsForScrolling:scrollDirection rangeMode:rangeMode rangeType:ASLayoutRangeTypePreload map:map]; - return; -} - -@end -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/ASTraceEvent.h b/submodules/AsyncDisplayKit/Source/Details/ASTraceEvent.h deleted file mode 100644 index 8ca7b32fa6..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASTraceEvent.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// ASTraceEvent.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED -@interface ASTraceEvent : NSObject - -/** - * This method is dealloc safe. - */ -- (instancetype)initWithBacktrace:(nullable NSArray *)backtrace - format:(NSString *)format - arguments:(va_list)arguments NS_FORMAT_FUNCTION(2,0); - -// Will be nil unless AS_SAVE_EVENT_BACKTRACES=1 (default=0) -@property (nonatomic, nullable, readonly) NSArray *backtrace; -@property (nonatomic, readonly) NSString *message; -@property (nonatomic, readonly) NSTimeInterval timestamp; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Details/ASTraceEvent.mm b/submodules/AsyncDisplayKit/Source/Details/ASTraceEvent.mm deleted file mode 100644 index c809865591..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/ASTraceEvent.mm +++ /dev/null @@ -1,67 +0,0 @@ -// -// ASTraceEvent.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -static NSString *const ASTraceEventThreadDescriptionKey = @"ASThreadTraceEventDescription"; - -@interface ASTraceEvent () -@property (nonatomic, readonly) NSString *objectDescription; -@property (nonatomic, readonly) NSString *threadDescription; -@end - -@implementation ASTraceEvent - -- (instancetype)initWithBacktrace:(NSArray *)backtrace format:(NSString *)format arguments:(va_list)args -{ - self = [super init]; - if (self != nil) { - static NSTimeInterval refTime; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - refTime = CACurrentMediaTime(); - }); - - // Create the format string passed to us. - _message = [[NSString alloc] initWithFormat:format arguments:args]; - - NSThread *thread = [NSThread currentThread]; - NSString *threadDescription = thread.name; - if (threadDescription.length == 0) { - if ([thread isMainThread]) { - threadDescription = @"Main"; - } else { - // If the bg thread has no name, we cache a 4-character ptr string to identify it by - // inside the thread dictionary. - NSMutableDictionary *threadDict = thread.threadDictionary; - threadDescription = threadDict[ASTraceEventThreadDescriptionKey]; - if (threadDescription == nil) { - // Want these to be 4-chars to line up with "Main". It's possible that a collision could happen - // here but it's so unbelievably likely to impact development, the risk is acceptable. - NSString *ptrString = [NSString stringWithFormat:@"%p", thread]; - threadDescription = [ptrString substringFromIndex:MAX(0, ptrString.length - 4)]; - threadDict[ASTraceEventThreadDescriptionKey] = threadDescription; - } - } - } - _threadDescription = threadDescription; - - _backtrace = backtrace; - _timestamp = CACurrentMediaTime() - refTime; - } - return self; -} - -- (NSString *)description -{ - return [NSString stringWithFormat:@"<(%@) t=%7.3f: %@>", _threadDescription, _timestamp, _message]; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/Details/NSMutableAttributedString+TextKitAdditions.h b/submodules/AsyncDisplayKit/Source/Details/NSMutableAttributedString+TextKitAdditions.h deleted file mode 100644 index afb96722c6..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/NSMutableAttributedString+TextKitAdditions.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// NSMutableAttributedString+TextKitAdditions.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface NSMutableAttributedString (TextKitAdditions) - -- (void)attributeTextInRange:(NSRange)range withTextKitMinimumLineHeight:(CGFloat)minimumLineHeight; - -- (void)attributeTextInRange:(NSRange)range withTextKitMinimumLineHeight:(CGFloat)minimumLineHeight maximumLineHeight:(CGFloat)maximumLineHeight; - -- (void)attributeTextInRange:(NSRange)range withTextKitLineHeight:(CGFloat)lineHeight; - -- (void)attributeTextInRange:(NSRange)range withTextKitParagraphStyle:(NSParagraphStyle *)paragraphStyle; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Details/NSMutableAttributedString+TextKitAdditions.mm b/submodules/AsyncDisplayKit/Source/Details/NSMutableAttributedString+TextKitAdditions.mm deleted file mode 100644 index 3f2ed9d35c..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/NSMutableAttributedString+TextKitAdditions.mm +++ /dev/null @@ -1,49 +0,0 @@ -// -// NSMutableAttributedString+TextKitAdditions.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@implementation NSMutableAttributedString (TextKitAdditions) - -#pragma mark - Convenience Methods - -- (void)attributeTextInRange:(NSRange)range withTextKitMinimumLineHeight:(CGFloat)minimumLineHeight -{ - if (range.length) { - - NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init]; - [style setMinimumLineHeight:minimumLineHeight]; - [self attributeTextInRange:range withTextKitParagraphStyle:style]; - } -} - -- (void)attributeTextInRange:(NSRange)range withTextKitMinimumLineHeight:(CGFloat)minimumLineHeight maximumLineHeight:(CGFloat)maximumLineHeight -{ - if (range.length) { - - NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init]; - [style setMinimumLineHeight:minimumLineHeight]; - [style setMaximumLineHeight:maximumLineHeight]; - [self attributeTextInRange:range withTextKitParagraphStyle:style]; - } -} - -- (void)attributeTextInRange:(NSRange)range withTextKitLineHeight:(CGFloat)lineHeight -{ - [self attributeTextInRange:range withTextKitMinimumLineHeight:lineHeight maximumLineHeight:lineHeight]; -} - -- (void)attributeTextInRange:(NSRange)range withTextKitParagraphStyle:(NSParagraphStyle *)paragraphStyle -{ - if (range.length) { - [self addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range]; - } -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/Details/UICollectionViewLayout+ASConvenience.h b/submodules/AsyncDisplayKit/Source/Details/UICollectionViewLayout+ASConvenience.h deleted file mode 100644 index 78e55a17bd..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/UICollectionViewLayout+ASConvenience.h +++ /dev/null @@ -1,28 +0,0 @@ -// -// UICollectionViewLayout+ASConvenience.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@protocol ASCollectionViewLayoutInspecting; - -NS_ASSUME_NONNULL_BEGIN - -@interface UICollectionViewLayout (ASLayoutInspectorProviding) - -/** - * You can override this method on your @c UICollectionViewLayout subclass to - * return a layout inspector tailored to your layout. - * - * It's fine to return @c self. You must not return @c nil. - */ -- (id)asdk_layoutInspector; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Details/UICollectionViewLayout+ASConvenience.mm b/submodules/AsyncDisplayKit/Source/Details/UICollectionViewLayout+ASConvenience.mm deleted file mode 100644 index d63b51a157..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/UICollectionViewLayout+ASConvenience.mm +++ /dev/null @@ -1,32 +0,0 @@ -// -// UICollectionViewLayout+ASConvenience.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -#import - -#import - -@implementation UICollectionViewLayout (ASLayoutInspectorProviding) - -- (id)asdk_layoutInspector -{ - UICollectionViewFlowLayout *flow = ASDynamicCast(self, UICollectionViewFlowLayout); - if (flow != nil) { - return [[ASCollectionViewFlowLayoutInspector alloc] initWithFlowLayout:flow]; - } else { - return [[ASCollectionViewLayoutInspector alloc] init]; - } -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/_ASCollectionReusableView.h b/submodules/AsyncDisplayKit/Source/Details/_ASCollectionReusableView.h deleted file mode 100644 index bb38bb25bf..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/_ASCollectionReusableView.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// _ASCollectionReusableView.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import - -@class ASCellNode, ASCollectionElement; - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED // Note: ASDynamicCastStrict is used on instances of this class based on this restriction. -@interface _ASCollectionReusableView : UICollectionReusableView - -@property (nullable, nonatomic, readonly) ASCellNode *node; -@property (nullable, nonatomic) ASCollectionElement *element; -@property (nullable, nonatomic) UICollectionViewLayoutAttributes *layoutAttributes; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/_ASCollectionReusableView.mm b/submodules/AsyncDisplayKit/Source/Details/_ASCollectionReusableView.mm deleted file mode 100644 index 6f5445654f..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/_ASCollectionReusableView.mm +++ /dev/null @@ -1,93 +0,0 @@ -// -// _ASCollectionReusableView.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -#import -#import - -@implementation _ASCollectionReusableView - -- (ASCellNode *)node -{ - return self.element.node; -} - -- (void)setElement:(ASCollectionElement *)element -{ - ASDisplayNodeAssertMainThread(); - element.node.layoutAttributes = _layoutAttributes; - _element = element; -} - -- (void)setLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes -{ - _layoutAttributes = layoutAttributes; - self.node.layoutAttributes = layoutAttributes; -} - -- (void)prepareForReuse -{ - self.layoutAttributes = nil; - - // Need to clear element before UIKit calls setSelected:NO / setHighlighted:NO on its cells - self.element = nil; - [super prepareForReuse]; -} - -/** - * In the initial case, this is called by UICollectionView during cell dequeueing, before - * we get a chance to assign a node to it, so we must be sure to set these layout attributes - * on our node when one is next assigned to us in @c setNode: . Since there may be cases when we _do_ already - * have our node assigned e.g. during a layout update for existing cells, we also attempt - * to update it now. - */ -- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes -{ - self.layoutAttributes = layoutAttributes; -} - -/** - * Keep our node filling our content view. - */ -- (void)layoutSubviews -{ - [super layoutSubviews]; - self.node.frame = self.bounds; -} - -@end - -/** - * A category that makes _ASCollectionReusableView conform to IGListBindable. - * - * We don't need to do anything to bind the view model – the cell node - * serves the same purpose. - */ -#if __has_include() - -#import - -@interface _ASCollectionReusableView (IGListBindable) -@end - -@implementation _ASCollectionReusableView (IGListBindable) - -- (void)bindViewModel:(id)viewModel -{ - // nop -} - -@end - -#endif - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/_ASCollectionViewCell.h b/submodules/AsyncDisplayKit/Source/Details/_ASCollectionViewCell.h deleted file mode 100644 index cf08d0d508..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/_ASCollectionViewCell.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// _ASCollectionViewCell.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import -#import - -@class ASCollectionElement; - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED // Note: ASDynamicCastStrict is used on instances of this class based on this restriction. -@interface _ASCollectionViewCell : UICollectionViewCell - -@property (nonatomic, nullable) ASCollectionElement *element; -@property (nullable, nonatomic, readonly) ASCellNode *node; -@property (nonatomic, nullable) UICollectionViewLayoutAttributes *layoutAttributes; - -/** - * Whether or not this cell is interested in cell node visibility events. - * -cellNodeVisibilityEvent:inScrollView: should be called only if this property is YES. - */ -@property (nonatomic, readonly) BOOL consumesCellNodeVisibilityEvents; - -- (void)cellNodeVisibilityEvent:(ASCellNodeVisibilityEvent)event inScrollView:(UIScrollView *)scrollView; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Details/_ASCollectionViewCell.mm b/submodules/AsyncDisplayKit/Source/Details/_ASCollectionViewCell.mm deleted file mode 100644 index a7f0b55d3f..0000000000 --- a/submodules/AsyncDisplayKit/Source/Details/_ASCollectionViewCell.mm +++ /dev/null @@ -1,149 +0,0 @@ -// -// _ASCollectionViewCell.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK -#import -#import - -#import -#import -#import - -@implementation _ASCollectionViewCell - -- (ASCellNode *)node -{ - return self.element.node; -} - -- (void)setElement:(ASCollectionElement *)element -{ - ASDisplayNodeAssertMainThread(); - ASCellNode *node = element.node; - node.layoutAttributes = _layoutAttributes; - _element = element; - - [node __setSelectedFromUIKit:self.selected]; - [node __setHighlightedFromUIKit:self.highlighted]; -} - -- (BOOL)consumesCellNodeVisibilityEvents -{ - ASCellNode *node = self.node; - if (node == nil) { - return NO; - } - return ASSubclassOverridesSelector([ASCellNode class], [node class], @selector(cellNodeVisibilityEvent:inScrollView:withCellFrame:)); -} - -- (void)cellNodeVisibilityEvent:(ASCellNodeVisibilityEvent)event inScrollView:(UIScrollView *)scrollView -{ - [self.node cellNodeVisibilityEvent:event inScrollView:scrollView withCellFrame:self.frame]; -} - -- (void)setSelected:(BOOL)selected -{ - [super setSelected:selected]; - [self.node __setSelectedFromUIKit:selected]; -} - -- (void)setHighlighted:(BOOL)highlighted -{ - [super setHighlighted:highlighted]; - [self.node __setHighlightedFromUIKit:highlighted]; -} - -- (void)setLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes -{ - _layoutAttributes = layoutAttributes; - self.node.layoutAttributes = layoutAttributes; -} - -- (void)prepareForReuse -{ - self.layoutAttributes = nil; - - // Need to clear element before UIKit calls setSelected:NO / setHighlighted:NO on its cells - self.element = nil; - [super prepareForReuse]; -} - -/** - * In the initial case, this is called by UICollectionView during cell dequeueing, before - * we get a chance to assign a node to it, so we must be sure to set these layout attributes - * on our node when one is next assigned to us in @c setNode: . Since there may be cases when we _do_ already - * have our node assigned e.g. during a layout update for existing cells, we also attempt - * to update it now. - */ -- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes -{ - [super applyLayoutAttributes:layoutAttributes]; - self.layoutAttributes = layoutAttributes; -} - -/** - * Keep our node filling our content view. - */ -- (void)layoutSubviews -{ - [super layoutSubviews]; - self.node.frame = self.contentView.bounds; -} - -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event -{ - /** - * The documentation for hitTest:withEvent: on an UIView explicitly states the fact that: - * it ignores view objects that are hidden, that have disabled user interactions, or have an - * alpha level less than 0.01. - * To be able to determine if the collection view cell should skip going further down the tree - * based on the states above we use a valid point within the cells bounds and check the - * superclass hitTest:withEvent: implementation. If this returns a valid value we can go on with - * checking the node as it's expected to not be in one of these states. - */ - if (![super hitTest:self.bounds.origin withEvent:event]) { - return nil; - } - - CGPoint pointOnNode = [self.node.view convertPoint:point fromView:self]; - return [self.node hitTest:pointOnNode withEvent:event]; -} - -- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event -{ - CGPoint pointOnNode = [self.node.view convertPoint:point fromView:self]; - return [self.node pointInside:pointOnNode withEvent:event]; -} - -@end - -/** - * A category that makes _ASCollectionViewCell conform to IGListBindable. - * - * We don't need to do anything to bind the view model – the cell node - * serves the same purpose. - */ -#if __has_include() - -#import - -@interface _ASCollectionViewCell (IGListBindable) -@end - -@implementation _ASCollectionViewCell (IGListBindable) - -- (void)bindViewModel:(id)viewModel -{ - // nop -} - -@end - -#endif -#endif diff --git a/submodules/AsyncDisplayKit/Source/IGListAdapter+AsyncDisplayKit.h b/submodules/AsyncDisplayKit/Source/IGListAdapter+AsyncDisplayKit.h deleted file mode 100644 index 76b53c1933..0000000000 --- a/submodules/AsyncDisplayKit/Source/IGListAdapter+AsyncDisplayKit.h +++ /dev/null @@ -1,37 +0,0 @@ -// -// IGListAdapter+AsyncDisplayKit.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_IG_LIST_KIT - -#import - -NS_ASSUME_NONNULL_BEGIN - -@class ASCollectionNode; - -@interface IGListAdapter (AsyncDisplayKit) - -/** - * Connect this list adapter to the given collection node. - * - * @param collectionNode The collection node to drive with this list adapter. - * - * @note This method may only be called once per list adapter, - * and it must be called on the main thread. -[UIViewController init] - * is a good place to call it. This method does not retain the collection node. - */ -- (void)setASDKCollectionNode:(ASCollectionNode *)collectionNode; - -@end - -NS_ASSUME_NONNULL_END - -#endif // AS_IG_LIST_KIT diff --git a/submodules/AsyncDisplayKit/Source/IGListAdapter+AsyncDisplayKit.mm b/submodules/AsyncDisplayKit/Source/IGListAdapter+AsyncDisplayKit.mm deleted file mode 100644 index c3e81d3e33..0000000000 --- a/submodules/AsyncDisplayKit/Source/IGListAdapter+AsyncDisplayKit.mm +++ /dev/null @@ -1,53 +0,0 @@ -// -// IGListAdapter+AsyncDisplayKit.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_IG_LIST_KIT - -#import -#import -#import -#import - -@implementation IGListAdapter (AsyncDisplayKit) - -- (void)setASDKCollectionNode:(ASCollectionNode *)collectionNode -{ - ASDisplayNodeAssertMainThread(); - - // Attempt to retrieve previous data source. - ASIGListAdapterBasedDataSource *dataSource = objc_getAssociatedObject(self, _cmd); - // Bomb if we already made one. - if (dataSource != nil) { - ASDisplayNodeFailAssert(@"Attempt to call %@ multiple times on the same list adapter. Not currently allowed!", NSStringFromSelector(_cmd)); - return; - } - - // Make a data source and retain it. - dataSource = [[ASIGListAdapterBasedDataSource alloc] initWithListAdapter:self collectionDelegate:collectionNode.delegate]; - objc_setAssociatedObject(self, _cmd, dataSource, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - - // Attach the data source to the collection node. - collectionNode.dataSource = dataSource; - collectionNode.delegate = dataSource; - __weak IGListAdapter *weakSelf = self; - [collectionNode onDidLoad:^(__kindof ASCollectionNode * _Nonnull collectionNode) { -#if IG_LIST_COLLECTION_VIEW - // We manually set the superclass of ASCollectionView to IGListCollectionView at runtime if needed. - weakSelf.collectionView = (IGListCollectionView *)collectionNode.view; -#else - weakSelf.collectionView = collectionNode.view; -#endif - }]; -} - -@end - -#endif // AS_IG_LIST_KIT diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASAbsoluteLayoutSpec.h b/submodules/AsyncDisplayKit/Source/Layout/ASAbsoluteLayoutSpec.h deleted file mode 100644 index 61bfbf8cd5..0000000000 --- a/submodules/AsyncDisplayKit/Source/Layout/ASAbsoluteLayoutSpec.h +++ /dev/null @@ -1,45 +0,0 @@ -// -// ASAbsoluteLayoutSpec.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -/** How much space the spec will take up. */ -typedef NS_ENUM(NSInteger, ASAbsoluteLayoutSpecSizing) { - /** The spec will take up the maximum size possible. */ - ASAbsoluteLayoutSpecSizingDefault, - /** Computes a size for the spec that is the union of all childrens' frames. */ - ASAbsoluteLayoutSpecSizingSizeToFit, -}; - -NS_ASSUME_NONNULL_BEGIN - -/** - A layout spec that positions children at fixed positions. - */ -@interface ASAbsoluteLayoutSpec : ASLayoutSpec - -/** - How much space will the spec taken up - */ -@property (nonatomic) ASAbsoluteLayoutSpecSizing sizing; - -/** - @param sizing How much space the spec will take up - @param children Children to be positioned at fixed positions - */ -+ (instancetype)absoluteLayoutSpecWithSizing:(ASAbsoluteLayoutSpecSizing)sizing children:(NSArray> *)children NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -/** - @param children Children to be positioned at fixed positions - */ -+ (instancetype)absoluteLayoutSpecWithChildren:(NSArray> *)children NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASAbsoluteLayoutSpec.mm b/submodules/AsyncDisplayKit/Source/Layout/ASAbsoluteLayoutSpec.mm deleted file mode 100644 index 18a3a3a51c..0000000000 --- a/submodules/AsyncDisplayKit/Source/Layout/ASAbsoluteLayoutSpec.mm +++ /dev/null @@ -1,104 +0,0 @@ -// -// ASAbsoluteLayoutSpec.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import -#import -#import -#import - -#pragma mark - ASAbsoluteLayoutSpec - -@implementation ASAbsoluteLayoutSpec - -#pragma mark - Class - -+ (instancetype)absoluteLayoutSpecWithChildren:(NSArray *)children NS_RETURNS_RETAINED -{ - return [[self alloc] initWithChildren:children]; -} - -+ (instancetype)absoluteLayoutSpecWithSizing:(ASAbsoluteLayoutSpecSizing)sizing children:(NSArray> *)children NS_RETURNS_RETAINED -{ - return [[self alloc] initWithSizing:sizing children:children]; -} - -#pragma mark - Lifecycle - -- (instancetype)init -{ - return [self initWithChildren:nil]; -} - -- (instancetype)initWithChildren:(NSArray *)children -{ - return [self initWithSizing:ASAbsoluteLayoutSpecSizingDefault children:children]; -} - -- (instancetype)initWithSizing:(ASAbsoluteLayoutSpecSizing)sizing children:(NSArray> *)children -{ - if (!(self = [super init])) { - return nil; - } - - _sizing = sizing; - self.children = children; - - return self; -} - -#pragma mark - ASLayoutSpec - -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize -{ - CGSize size = { - ASPointsValidForSize(constrainedSize.max.width) == NO ? ASLayoutElementParentDimensionUndefined : constrainedSize.max.width, - ASPointsValidForSize(constrainedSize.max.height) == NO ? ASLayoutElementParentDimensionUndefined : constrainedSize.max.height - }; - - NSArray *children = self.children; - ASLayout *rawSublayouts[children.count]; - int i = 0; - - for (id child in children) { - CGPoint layoutPosition = child.style.layoutPosition; - CGSize autoMaxSize = { - constrainedSize.max.width - layoutPosition.x, - constrainedSize.max.height - layoutPosition.y - }; - - const ASSizeRange childConstraint = ASLayoutElementSizeResolveAutoSize(child.style.size, size, {{0,0}, autoMaxSize}); - - ASLayout *sublayout = [child layoutThatFits:childConstraint parentSize:size]; - sublayout.position = layoutPosition; - rawSublayouts[i++] = sublayout; - } - const auto sublayouts = [NSArray arrayByTransferring:rawSublayouts count:i]; - - if (_sizing == ASAbsoluteLayoutSpecSizingSizeToFit || isnan(size.width)) { - size.width = constrainedSize.min.width; - for (ASLayout *sublayout in sublayouts) { - size.width = MAX(size.width, sublayout.position.x + sublayout.size.width); - } - } - - if (_sizing == ASAbsoluteLayoutSpecSizingSizeToFit || isnan(size.height)) { - size.height = constrainedSize.min.height; - for (ASLayout *sublayout in sublayouts) { - size.height = MAX(size.height, sublayout.position.y + sublayout.size.height); - } - } - - return [ASLayout layoutWithLayoutElement:self size:ASSizeRangeClamp(constrainedSize, size) sublayouts:sublayouts]; -} - -@end - diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASBackgroundLayoutSpec.h b/submodules/AsyncDisplayKit/Source/Layout/ASBackgroundLayoutSpec.h deleted file mode 100644 index 032a240bbd..0000000000 --- a/submodules/AsyncDisplayKit/Source/Layout/ASBackgroundLayoutSpec.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// ASBackgroundLayoutSpec.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - Lays out a single layoutElement child, then lays out a background layoutElement instance behind it stretched to its size. - */ -@interface ASBackgroundLayoutSpec : ASLayoutSpec - -/** - * Background layoutElement for this layout spec - */ -@property (nonatomic) id background; - -/** - * Creates and returns an ASBackgroundLayoutSpec object - * - * @param child A child that is laid out to determine the size of this spec. - * @param background A layoutElement object that is laid out behind the child. - */ -+ (instancetype)backgroundLayoutSpecWithChild:(id)child background:(id)background NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASBackgroundLayoutSpec.mm b/submodules/AsyncDisplayKit/Source/Layout/ASBackgroundLayoutSpec.mm deleted file mode 100644 index 31dd75487a..0000000000 --- a/submodules/AsyncDisplayKit/Source/Layout/ASBackgroundLayoutSpec.mm +++ /dev/null @@ -1,92 +0,0 @@ -// -// ASBackgroundLayoutSpec.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -#import -#import - -static NSUInteger const kForegroundChildIndex = 0; -static NSUInteger const kBackgroundChildIndex = 1; - -@implementation ASBackgroundLayoutSpec - -#pragma mark - Class - -+ (instancetype)backgroundLayoutSpecWithChild:(id)child background:(id)background NS_RETURNS_RETAINED -{ - return [[self alloc] initWithChild:child background:background]; -} - -#pragma mark - Lifecycle - -- (instancetype)initWithChild:(id)child background:(id)background -{ - if (!(self = [super init])) { - return nil; - } - self.child = child; - self.background = background; - return self; -} - -#pragma mark - ASLayoutSpec - -/** - * First layout the contents, then fit the background image. - */ -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize - restrictedToSize:(ASLayoutElementSize)size - relativeToParentSize:(CGSize)parentSize -{ - ASLayout *contentsLayout = [self.child layoutThatFits:constrainedSize parentSize:parentSize]; - - ASLayout *rawSublayouts[2]; - int i = 0; - if (self.background) { - // Size background to exactly the same size. - ASLayout *backgroundLayout = [self.background layoutThatFits:ASSizeRangeMake(contentsLayout.size) - parentSize:parentSize]; - backgroundLayout.position = CGPointZero; - rawSublayouts[i++] = backgroundLayout; - } - contentsLayout.position = CGPointZero; - rawSublayouts[i++] = contentsLayout; - - const auto sublayouts = [NSArray arrayByTransferring:rawSublayouts count:i]; - return [ASLayout layoutWithLayoutElement:self size:contentsLayout.size sublayouts:sublayouts]; -} - -#pragma mark - Background - -- (void)setChild:(id)child -{ - ASDisplayNodeAssertNotNil(child, @"Child cannot be nil"); - [super setChild:child atIndex:kForegroundChildIndex]; -} - -- (id)child -{ - return [super childAtIndex:kForegroundChildIndex]; -} - -- (void)setBackground:(id)background -{ - ASDisplayNodeAssertNotNil(background, @"Background cannot be nil"); - [super setChild:background atIndex:kBackgroundChildIndex]; -} - -- (id)background -{ - return [super childAtIndex:kBackgroundChildIndex]; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASCenterLayoutSpec.h b/submodules/AsyncDisplayKit/Source/Layout/ASCenterLayoutSpec.h deleted file mode 100644 index dd1f99aa5a..0000000000 --- a/submodules/AsyncDisplayKit/Source/Layout/ASCenterLayoutSpec.h +++ /dev/null @@ -1,70 +0,0 @@ -// -// ASCenterLayoutSpec.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -/** - * How the child is centered within the spec. - * - * The default option will position the child at {0,0} relatively to the layout bound. - * Swift: use [] for the default behavior. - */ -typedef NS_OPTIONS(NSUInteger, ASCenterLayoutSpecCenteringOptions) { - /** The child is positioned in {0,0} relatively to the layout bounds */ - ASCenterLayoutSpecCenteringNone = 0, - /** The child is centered along the X axis */ - ASCenterLayoutSpecCenteringX = 1 << 0, - /** The child is centered along the Y axis */ - ASCenterLayoutSpecCenteringY = 1 << 1, - /** Convenience option to center both along the X and Y axis */ - ASCenterLayoutSpecCenteringXY = ASCenterLayoutSpecCenteringX | ASCenterLayoutSpecCenteringY -}; - -/** - * How much space the spec will take up. - * - * The default option will allow the spec to take up the maximum size possible. - * Swift: use [] for the default behavior. - */ -typedef NS_OPTIONS(NSUInteger, ASCenterLayoutSpecSizingOptions) { - /** The spec will take up the maximum size possible */ - ASCenterLayoutSpecSizingOptionDefault = ASRelativeLayoutSpecSizingOptionDefault, - /** The spec will take up the minimum size possible along the X axis */ - ASCenterLayoutSpecSizingOptionMinimumX = ASRelativeLayoutSpecSizingOptionMinimumWidth, - /** The spec will take up the minimum size possible along the Y axis */ - ASCenterLayoutSpecSizingOptionMinimumY = ASRelativeLayoutSpecSizingOptionMinimumHeight, - /** Convenience option to take up the minimum size along both the X and Y axis */ - ASCenterLayoutSpecSizingOptionMinimumXY = ASRelativeLayoutSpecSizingOptionMinimumSize -}; - -NS_ASSUME_NONNULL_BEGIN - -/** Lays out a single layoutElement child and position it so that it is centered into the layout bounds. - * NOTE: ASRelativeLayoutSpec offers all of the capabilities of Center, and more. - * Check it out if you would like to be able to position the child at any corner or the middle of an edge. - */ -@interface ASCenterLayoutSpec : ASRelativeLayoutSpec - -@property (nonatomic) ASCenterLayoutSpecCenteringOptions centeringOptions; -@property (nonatomic) ASCenterLayoutSpecSizingOptions sizingOptions; - -/** - * Initializer. - * - * @param centeringOptions How the child is centered. - * @param sizingOptions How much space will be taken up. - * @param child The child to center. - */ -+ (instancetype)centerLayoutSpecWithCenteringOptions:(ASCenterLayoutSpecCenteringOptions)centeringOptions - sizingOptions:(ASCenterLayoutSpecSizingOptions)sizingOptions - child:(id)child NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASCenterLayoutSpec.mm b/submodules/AsyncDisplayKit/Source/Layout/ASCenterLayoutSpec.mm deleted file mode 100644 index 107f317c0c..0000000000 --- a/submodules/AsyncDisplayKit/Source/Layout/ASCenterLayoutSpec.mm +++ /dev/null @@ -1,76 +0,0 @@ -// -// ASCenterLayoutSpec.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -@implementation ASCenterLayoutSpec -{ - ASCenterLayoutSpecCenteringOptions _centeringOptions; - ASCenterLayoutSpecSizingOptions _sizingOptions; -} - -- (instancetype)initWithCenteringOptions:(ASCenterLayoutSpecCenteringOptions)centeringOptions - sizingOptions:(ASCenterLayoutSpecSizingOptions)sizingOptions - child:(id)child; -{ - ASRelativeLayoutSpecPosition verticalPosition = [self verticalPositionFromCenteringOptions:centeringOptions]; - ASRelativeLayoutSpecPosition horizontalPosition = [self horizontalPositionFromCenteringOptions:centeringOptions]; - - if (!(self = [super initWithHorizontalPosition:horizontalPosition verticalPosition:verticalPosition sizingOption:sizingOptions child:child])) { - return nil; - } - _centeringOptions = centeringOptions; - _sizingOptions = sizingOptions; - return self; -} - -+ (instancetype)centerLayoutSpecWithCenteringOptions:(ASCenterLayoutSpecCenteringOptions)centeringOptions - sizingOptions:(ASCenterLayoutSpecSizingOptions)sizingOptions - child:(id)child NS_RETURNS_RETAINED -{ - return [[self alloc] initWithCenteringOptions:centeringOptions sizingOptions:sizingOptions child:child]; -} - -- (void)setCenteringOptions:(ASCenterLayoutSpecCenteringOptions)centeringOptions -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - _centeringOptions = centeringOptions; - - [self setHorizontalPosition:[self horizontalPositionFromCenteringOptions:centeringOptions]]; - [self setVerticalPosition:[self verticalPositionFromCenteringOptions:centeringOptions]]; -} - -- (void)setSizingOptions:(ASCenterLayoutSpecSizingOptions)sizingOptions -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - _sizingOptions = sizingOptions; - [self setSizingOption:sizingOptions]; -} - -- (ASRelativeLayoutSpecPosition)horizontalPositionFromCenteringOptions:(ASCenterLayoutSpecCenteringOptions)centeringOptions -{ - if ((centeringOptions & ASCenterLayoutSpecCenteringX) != 0) { - return ASRelativeLayoutSpecPositionCenter; - } else { - return ASRelativeLayoutSpecPositionNone; - } -} - -- (ASRelativeLayoutSpecPosition)verticalPositionFromCenteringOptions:(ASCenterLayoutSpecCenteringOptions)centeringOptions -{ - if ((centeringOptions & ASCenterLayoutSpecCenteringY) != 0) { - return ASRelativeLayoutSpecPositionCenter; - } else { - return ASRelativeLayoutSpecPositionNone; - } -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASCornerLayoutSpec.h b/submodules/AsyncDisplayKit/Source/Layout/ASCornerLayoutSpec.h deleted file mode 100644 index 799c9c28c3..0000000000 --- a/submodules/AsyncDisplayKit/Source/Layout/ASCornerLayoutSpec.h +++ /dev/null @@ -1,75 +0,0 @@ -// -// ASCornerLayoutSpec.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -/** - The corner location for positioning corner element. - */ -typedef NS_ENUM(NSInteger, ASCornerLayoutLocation) { - ASCornerLayoutLocationTopLeft, - ASCornerLayoutLocationTopRight, - ASCornerLayoutLocationBottomLeft, - ASCornerLayoutLocationBottomRight, -}; - -NS_ASSUME_NONNULL_BEGIN - -/** - A layout spec that positions a corner element which relatives to the child element. - - @warning Both child element and corner element must have valid preferredSize for layout calculation. - */ -@interface ASCornerLayoutSpec : ASLayoutSpec - -/** - A layout spec that positions a corner element which relatives to the child element. - - @param child A child that is laid out to determine the size of this spec. - @param corner A layoutElement object that is laid out to a corner on the child. - @param location The corner position option. - @return An ASCornerLayoutSpec object with a given child and an layoutElement that act as corner. - */ -- (instancetype)initWithChild:(id )child corner:(id )corner location:(ASCornerLayoutLocation)location AS_WARN_UNUSED_RESULT; - -/** - A layout spec that positions a corner element which relatives to the child element. - - @param child A child that is laid out to determine the size of this spec. - @param corner A layoutElement object that is laid out to a corner on the child. - @param location The corner position option. - @return An ASCornerLayoutSpec object with a given child and an layoutElement that act as corner. - */ -+ (instancetype)cornerLayoutSpecWithChild:(id )child corner:(id )corner location:(ASCornerLayoutLocation)location NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -/** - A layoutElement object that is laid out to a corner on the child. - */ -@property (nonatomic) id corner; - -/** - The corner position option. - */ -@property (nonatomic) ASCornerLayoutLocation cornerLocation; - -/** - The point which offsets from the corner location. Use this property to make delta - distance from the default corner location. Default is CGPointZero. - */ -@property (nonatomic) CGPoint offset; - -/** - Whether should include corner element into layout size calculation. If included, - the layout size will be the union size of both child and corner; If not included, - the layout size will be only child's size. Default is NO. - */ -@property (nonatomic) BOOL wrapsCorner; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASCornerLayoutSpec.mm b/submodules/AsyncDisplayKit/Source/Layout/ASCornerLayoutSpec.mm deleted file mode 100644 index 6663b28935..0000000000 --- a/submodules/AsyncDisplayKit/Source/Layout/ASCornerLayoutSpec.mm +++ /dev/null @@ -1,165 +0,0 @@ -// -// ASCornerLayoutSpec.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import -#import - -CGPoint as_calculatedCornerOriginIn(CGRect baseFrame, CGSize cornerSize, ASCornerLayoutLocation cornerLocation, CGPoint offset) -{ - CGPoint cornerOrigin = CGPointZero; - CGPoint baseOrigin = baseFrame.origin; - CGSize baseSize = baseFrame.size; - - switch (cornerLocation) { - case ASCornerLayoutLocationTopLeft: - cornerOrigin.x = baseOrigin.x - cornerSize.width / 2; - cornerOrigin.y = baseOrigin.y - cornerSize.height / 2; - break; - case ASCornerLayoutLocationTopRight: - cornerOrigin.x = baseOrigin.x + baseSize.width - cornerSize.width / 2; - cornerOrigin.y = baseOrigin.y - cornerSize.height / 2; - break; - case ASCornerLayoutLocationBottomLeft: - cornerOrigin.x = baseOrigin.x - cornerSize.width / 2; - cornerOrigin.y = baseOrigin.y + baseSize.height - cornerSize.height / 2; - break; - case ASCornerLayoutLocationBottomRight: - cornerOrigin.x = baseOrigin.x + baseSize.width - cornerSize.width / 2; - cornerOrigin.y = baseOrigin.y + baseSize.height - cornerSize.height / 2; - break; - } - - cornerOrigin.x += offset.x; - cornerOrigin.y += offset.y; - - return cornerOrigin; -} - -static NSUInteger const kBaseChildIndex = 0; -static NSUInteger const kCornerChildIndex = 1; - -@interface ASCornerLayoutSpec() -@end - -@implementation ASCornerLayoutSpec - -- (instancetype)initWithChild:(id )child corner:(id )corner location:(ASCornerLayoutLocation)location -{ - self = [super init]; - if (self) { - self.child = child; - self.corner = corner; - self.cornerLocation = location; - } - return self; -} - -+ (instancetype)cornerLayoutSpecWithChild:(id )child corner:(id )corner location:(ASCornerLayoutLocation)location NS_RETURNS_RETAINED -{ - return [[self alloc] initWithChild:child corner:corner location:location]; -} - -#pragma mark - Children - -- (void)setChild:(id)child -{ - ASDisplayNodeAssertNotNil(child, @"Child shouldn't be nil."); - [super setChild:child atIndex:kBaseChildIndex]; -} - -- (id)child -{ - return [super childAtIndex:kBaseChildIndex]; -} - -- (void)setCorner:(id)corner -{ - ASDisplayNodeAssertNotNil(corner, @"Corner element cannot be nil."); - [super setChild:corner atIndex:kCornerChildIndex]; -} - -- (id)corner -{ - return [super childAtIndex:kCornerChildIndex]; -} - -#pragma mark - Calculation - -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize -{ - CGSize size = { - ASPointsValidForSize(constrainedSize.max.width) == NO ? ASLayoutElementParentDimensionUndefined : constrainedSize.max.width, - ASPointsValidForSize(constrainedSize.max.height) == NO ? ASLayoutElementParentDimensionUndefined : constrainedSize.max.height - }; - - id child = self.child; - id corner = self.corner; - - // Element validation - [self _validateElement:child]; - [self _validateElement:corner]; - - CGRect childFrame = CGRectZero; - CGRect cornerFrame = CGRectZero; - - // Layout child - ASLayout *childLayout = [child layoutThatFits:constrainedSize parentSize:size]; - childFrame.size = childLayout.size; - - // Layout corner - ASLayout *cornerLayout = [corner layoutThatFits:constrainedSize parentSize:size]; - cornerFrame.size = cornerLayout.size; - - // Calculate corner's position - CGPoint relativePosition = as_calculatedCornerOriginIn(childFrame, cornerFrame.size, _cornerLocation, _offset); - - // Update corner's position - cornerFrame.origin = relativePosition; - - // Calculate size - CGRect frame = childFrame; - if (_wrapsCorner) { - frame = CGRectUnion(childFrame, cornerFrame); - frame.size = ASSizeRangeClamp(constrainedSize, frame.size); - } - - // Shift sublayouts' positions if they are off the bounds. - if (frame.origin.x != 0) { - CGFloat deltaX = frame.origin.x; - childFrame.origin.x -= deltaX; - cornerFrame.origin.x -= deltaX; - } - - if (frame.origin.y != 0) { - CGFloat deltaY = frame.origin.y; - childFrame.origin.y -= deltaY; - cornerFrame.origin.y -= deltaY; - } - - childLayout.position = childFrame.origin; - cornerLayout.position = cornerFrame.origin; - - return [ASLayout layoutWithLayoutElement:self size:frame.size sublayouts:@[childLayout, cornerLayout]]; -} - -- (void)_validateElement:(id )element -{ - // Validate non-nil element - if (element == nil) { - ASDisplayNodeAssertNotNil(element, @"[%@]: Must have a non-nil child/corner for layout calculation.", self.class); - } - // Validate preferredSize if needed - CGSize size = element.style.preferredSize; - if (!CGSizeEqualToSize(size, CGSizeZero) && !ASIsCGSizeValidForSize(size) && (size.width < 0 || (size.height < 0))) { - ASDisplayNodeFailAssert(@"[%@]: Should give a valid preferredSize value for %@ before corner's position calculation.", self.class, element); - } -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASInsetLayoutSpec.h b/submodules/AsyncDisplayKit/Source/Layout/ASInsetLayoutSpec.h deleted file mode 100644 index 8e22fe9bd2..0000000000 --- a/submodules/AsyncDisplayKit/Source/Layout/ASInsetLayoutSpec.h +++ /dev/null @@ -1,43 +0,0 @@ -// -// ASInsetLayoutSpec.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - A layout spec that wraps another layoutElement child, applying insets around it. - - If the child has a size specified as a fraction, the fraction is resolved against this spec's parent - size **after** applying insets. - - @example ASOuterLayoutSpec contains an ASInsetLayoutSpec with an ASInnerLayoutSpec. Suppose that: - - ASOuterLayoutSpec is 200pt wide. - - ASInnerLayoutSpec specifies its width as 100%. - - The ASInsetLayoutSpec has insets of 10pt on every side. - ASInnerLayoutSpec will have size 180pt, not 200pt, because it receives a parent size that has been adjusted for insets. - - If you're familiar with CSS: ASInsetLayoutSpec's child behaves similarly to "box-sizing: border-box". - - An infinite inset is resolved as an inset equal to all remaining space after applying the other insets and child size. - @example An ASInsetLayoutSpec with an infinite left inset and 10px for all other edges will position it's child 10px from the right edge. - */ -@interface ASInsetLayoutSpec : ASLayoutSpec - -@property (nonatomic) UIEdgeInsets insets; - -/** - @param insets The amount of space to inset on each side. - @param child The wrapped child to inset. - */ -+ (instancetype)insetLayoutSpecWithInsets:(UIEdgeInsets)insets child:(id)child NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASInsetLayoutSpec.mm b/submodules/AsyncDisplayKit/Source/Layout/ASInsetLayoutSpec.mm deleted file mode 100644 index 450480f1c4..0000000000 --- a/submodules/AsyncDisplayKit/Source/Layout/ASInsetLayoutSpec.mm +++ /dev/null @@ -1,123 +0,0 @@ -// -// ASInsetLayoutSpec.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -#import -#import - -@interface ASInsetLayoutSpec () -{ - UIEdgeInsets _insets; -} -@end - -/* Returns f if f is finite, substitute otherwise */ -static CGFloat finite(CGFloat f, CGFloat substitute) -{ - return isinf(f) ? substitute : f; -} - -/* Returns f if f is finite, 0 otherwise */ -static CGFloat finiteOrZero(CGFloat f) -{ - return finite(f, 0); -} - -/* Returns the inset required to center 'inner' in 'outer' */ -static CGFloat centerInset(CGFloat outer, CGFloat inner) -{ - return ASRoundPixelValue((outer - inner) / 2); -} - -@implementation ASInsetLayoutSpec - -- (instancetype)initWithInsets:(UIEdgeInsets)insets child:(id)child; -{ - if (!(self = [super init])) { - return nil; - } - ASDisplayNodeAssertNotNil(child, @"Child cannot be nil"); - _insets = insets; - [self setChild:child]; - return self; -} - -+ (instancetype)insetLayoutSpecWithInsets:(UIEdgeInsets)insets child:(id)child NS_RETURNS_RETAINED -{ - return [[self alloc] initWithInsets:insets child:child]; -} - -- (void)setInsets:(UIEdgeInsets)insets -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - _insets = insets; -} - -/** - Inset will compute a new constrained size for it's child after applying insets and re-positioning - the child to respect the inset. - */ -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize - restrictedToSize:(ASLayoutElementSize)size - relativeToParentSize:(CGSize)parentSize -{ - if (self.child == nil) { - ASDisplayNodeAssert(NO, @"Inset spec measured without a child. The spec will do nothing."); - return [ASLayout layoutWithLayoutElement:self size:CGSizeZero]; - } - - const CGFloat insetsX = (finiteOrZero(_insets.left) + finiteOrZero(_insets.right)); - const CGFloat insetsY = (finiteOrZero(_insets.top) + finiteOrZero(_insets.bottom)); - - // if either x-axis inset is infinite, let child be intrinsic width - const CGFloat minWidth = (isinf(_insets.left) || isinf(_insets.right)) ? 0 : constrainedSize.min.width; - // if either y-axis inset is infinite, let child be intrinsic height - const CGFloat minHeight = (isinf(_insets.top) || isinf(_insets.bottom)) ? 0 : constrainedSize.min.height; - - const ASSizeRange insetConstrainedSize = { - { - MAX(0, minWidth - insetsX), - MAX(0, minHeight - insetsY), - }, - { - MAX(0, constrainedSize.max.width - insetsX), - MAX(0, constrainedSize.max.height - insetsY), - } - }; - - const CGSize insetParentSize = { - MAX(0, parentSize.width - insetsX), - MAX(0, parentSize.height - insetsY) - }; - - ASLayout *sublayout = [self.child layoutThatFits:insetConstrainedSize parentSize:insetParentSize]; - - const CGSize computedSize = ASSizeRangeClamp(constrainedSize, { - finite(sublayout.size.width + _insets.left + _insets.right, constrainedSize.max.width), - finite(sublayout.size.height + _insets.top + _insets.bottom, constrainedSize.max.height), - }); - - const CGFloat x = finite(_insets.left, constrainedSize.max.width - - (finite(_insets.right, - centerInset(constrainedSize.max.width, sublayout.size.width)) + sublayout.size.width)); - - const CGFloat y = finite(_insets.top, - constrainedSize.max.height - - (finite(_insets.bottom, - centerInset(constrainedSize.max.height, sublayout.size.height)) + sublayout.size.height)); - - sublayout.position = CGPointMake(x, y); - - return [ASLayout layoutWithLayoutElement:self size:computedSize sublayouts:@[sublayout]]; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASLayout+IGListKit.h b/submodules/AsyncDisplayKit/Source/Layout/ASLayout+IGListKit.h deleted file mode 100644 index 6f6753da2c..0000000000 --- a/submodules/AsyncDisplayKit/Source/Layout/ASLayout+IGListKit.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// ASLayout+IGListKit.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#if AS_IG_LIST_KIT -#import -#import -@interface ASLayout(IGListKit) -@end - -#endif // AS_IG_LIST_KIT diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASLayout+IGListKit.mm b/submodules/AsyncDisplayKit/Source/Layout/ASLayout+IGListKit.mm deleted file mode 100644 index f9cc139e9c..0000000000 --- a/submodules/AsyncDisplayKit/Source/Layout/ASLayout+IGListKit.mm +++ /dev/null @@ -1,30 +0,0 @@ -// -// ASLayout+IGListKit.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// -#import -#if AS_IG_LIST_KIT -#import "ASLayout+IGListKit.h" - -@interface ASLayout() { -@public - id _layoutElement; -} -@end - -@implementation ASLayout(IGListKit) - -- (id )diffIdentifier -{ - return self->_layoutElement; -} - -- (BOOL)isEqualToDiffableObject:(id )other -{ - return [self isEqual:other]; -} -@end -#endif // AS_IG_LIST_KIT diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASLayoutElement.mm.orig b/submodules/AsyncDisplayKit/Source/Layout/ASLayoutElement.mm.orig deleted file mode 100644 index 3a17293bc9..0000000000 --- a/submodules/AsyncDisplayKit/Source/Layout/ASLayoutElement.mm.orig +++ /dev/null @@ -1,869 +0,0 @@ -// -// ASLayoutElement.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import -#import -#import -#import -#import - -#import -#include - -#if YOGA - #import YOGA_HEADER_PATH - #import -#endif - -#pragma mark - ASLayoutElementContext - -@implementation ASLayoutElementContext - -- (instancetype)init -{ - if (self = [super init]) { - _transitionID = ASLayoutElementContextDefaultTransitionID; - } - return self; -} - -@end - -CGFloat const ASLayoutElementParentDimensionUndefined = NAN; -CGSize const ASLayoutElementParentSizeUndefined = {ASLayoutElementParentDimensionUndefined, ASLayoutElementParentDimensionUndefined}; - -int32_t const ASLayoutElementContextInvalidTransitionID = 0; -int32_t const ASLayoutElementContextDefaultTransitionID = ASLayoutElementContextInvalidTransitionID + 1; - -<<<<<<< HEAD -#ifdef MINIMAL_ASDK -static ASLayoutElementContext *mainThreadTlsContext = nil; - -static ASLayoutElementContext *get_tls_context() { - if ([NSThread isMainThread]) { - return mainThreadTlsContext; - } else { - return [NSThread currentThread].threadDictionary[@"ASDK_tls_context"]; - } -} - -static void set_tls_context(ASLayoutElementContext *value) { - if ([NSThread isMainThread]) { - mainThreadTlsContext = value; - } else { - if (value != nil) { - [NSThread currentThread].threadDictionary[@"ASDK_tls_context"] = value; - } else { - [[NSThread currentThread].threadDictionary removeObjectForKey:@"ASDK_tls_context"]; - } - } -} -#else -======= -#if AS_TLS_AVAILABLE - ->>>>>>> 565da7d4935740d12fc204aa061faf093831da1e -static _Thread_local __unsafe_unretained ASLayoutElementContext *tls_context; -#endif - -void ASLayoutElementPushContext(ASLayoutElementContext *context) -{ -#ifdef MINIMAL_ASDK - // NOTE: It would be easy to support nested contexts – just use an NSMutableArray here. - ASDisplayNodeCAssertNil(get_tls_context(), @"Nested ASLayoutElementContexts aren't supported."); - - ; - set_tls_context(context); -#else - // NOTE: It would be easy to support nested contexts – just use an NSMutableArray here. - ASDisplayNodeCAssertNil(tls_context, @"Nested ASLayoutElementContexts aren't supported."); - - tls_context = (__bridge ASLayoutElementContext *)(__bridge_retained CFTypeRef)context; -#endif -} - -ASLayoutElementContext *ASLayoutElementGetCurrentContext() -{ - // Don't retain here. Caller will retain if it wants to! - return get_tls_context(); -} - -void ASLayoutElementPopContext() -{ -#ifdef MINIMAL_ASDK - ASDisplayNodeCAssertNotNil(get_tls_context(), @"Attempt to pop context when there wasn't a context!"); - set_tls_context(nil); -#else - ASDisplayNodeCAssertNotNil(tls_context, @"Attempt to pop context when there wasn't a context!"); - CFRelease((__bridge CFTypeRef)tls_context); - tls_context = nil; -#endif -} - -#else - -static pthread_key_t ASLayoutElementContextKey() { - static pthread_key_t k; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - pthread_key_create(&k, NULL); - }); - return k; -} -void ASLayoutElementPushContext(ASLayoutElementContext *context) -{ - // NOTE: It would be easy to support nested contexts – just use an NSMutableArray here. - ASDisplayNodeCAssertNil(pthread_getspecific(ASLayoutElementContextKey()), @"Nested ASLayoutElementContexts aren't supported."); - - let cfCtx = (__bridge_retained CFTypeRef)context; - pthread_setspecific(ASLayoutElementContextKey(), cfCtx); -} - -ASLayoutElementContext *ASLayoutElementGetCurrentContext() -{ - // Don't retain here. Caller will retain if it wants to! - let ctxPtr = pthread_getspecific(ASLayoutElementContextKey()); - return (__bridge ASLayoutElementContext *)ctxPtr; -} - -void ASLayoutElementPopContext() -{ - let ctx = (CFTypeRef)pthread_getspecific(ASLayoutElementContextKey()); - ASDisplayNodeCAssertNotNil(ctx, @"Attempt to pop context when there wasn't a context!"); - CFRelease(ctx); - pthread_setspecific(ASLayoutElementContextKey(), NULL); -} - -#endif // AS_TLS_AVAILABLE - -#pragma mark - ASLayoutElementStyle - -NSString * const ASLayoutElementStyleWidthProperty = @"ASLayoutElementStyleWidthProperty"; -NSString * const ASLayoutElementStyleMinWidthProperty = @"ASLayoutElementStyleMinWidthProperty"; -NSString * const ASLayoutElementStyleMaxWidthProperty = @"ASLayoutElementStyleMaxWidthProperty"; - -NSString * const ASLayoutElementStyleHeightProperty = @"ASLayoutElementStyleHeightProperty"; -NSString * const ASLayoutElementStyleMinHeightProperty = @"ASLayoutElementStyleMinHeightProperty"; -NSString * const ASLayoutElementStyleMaxHeightProperty = @"ASLayoutElementStyleMaxHeightProperty"; - -NSString * const ASLayoutElementStyleSpacingBeforeProperty = @"ASLayoutElementStyleSpacingBeforeProperty"; -NSString * const ASLayoutElementStyleSpacingAfterProperty = @"ASLayoutElementStyleSpacingAfterProperty"; -NSString * const ASLayoutElementStyleFlexGrowProperty = @"ASLayoutElementStyleFlexGrowProperty"; -NSString * const ASLayoutElementStyleFlexShrinkProperty = @"ASLayoutElementStyleFlexShrinkProperty"; -NSString * const ASLayoutElementStyleFlexBasisProperty = @"ASLayoutElementStyleFlexBasisProperty"; -NSString * const ASLayoutElementStyleAlignSelfProperty = @"ASLayoutElementStyleAlignSelfProperty"; -NSString * const ASLayoutElementStyleAscenderProperty = @"ASLayoutElementStyleAscenderProperty"; -NSString * const ASLayoutElementStyleDescenderProperty = @"ASLayoutElementStyleDescenderProperty"; - -NSString * const ASLayoutElementStyleLayoutPositionProperty = @"ASLayoutElementStyleLayoutPositionProperty"; - -#if YOGA -NSString * const ASYogaFlexWrapProperty = @"ASLayoutElementStyleLayoutFlexWrapProperty"; -NSString * const ASYogaFlexDirectionProperty = @"ASYogaFlexDirectionProperty"; -NSString * const ASYogaDirectionProperty = @"ASYogaDirectionProperty"; -NSString * const ASYogaSpacingProperty = @"ASYogaSpacingProperty"; -NSString * const ASYogaJustifyContentProperty = @"ASYogaJustifyContentProperty"; -NSString * const ASYogaAlignItemsProperty = @"ASYogaAlignItemsProperty"; -NSString * const ASYogaPositionTypeProperty = @"ASYogaPositionTypeProperty"; -NSString * const ASYogaPositionProperty = @"ASYogaPositionProperty"; -NSString * const ASYogaMarginProperty = @"ASYogaMarginProperty"; -NSString * const ASYogaPaddingProperty = @"ASYogaPaddingProperty"; -NSString * const ASYogaBorderProperty = @"ASYogaBorderProperty"; -NSString * const ASYogaAspectRatioProperty = @"ASYogaAspectRatioProperty"; -#endif - -#define ASLayoutElementStyleSetSizeWithScope(x) \ - __instanceLock__.lock(); \ - ASLayoutElementSize newSize = _size.load(); \ - { x } \ - _size.store(newSize); \ - __instanceLock__.unlock(); - -#define ASLayoutElementStyleCallDelegate(propertyName)\ -do {\ - [self propertyDidChange:propertyName];\ - [_delegate style:self propertyDidChange:propertyName];\ -} while(0) - -@implementation ASLayoutElementStyle { - ASDN::RecursiveMutex __instanceLock__; - ASLayoutElementStyleExtensions _extensions; - - std::atomic _size; - std::atomic _spacingBefore; - std::atomic _spacingAfter; - std::atomic _flexGrow; - std::atomic _flexShrink; - std::atomic _flexBasis; - std::atomic _alignSelf; - std::atomic _ascender; - std::atomic _descender; - std::atomic _layoutPosition; - -#if YOGA - YGNodeRef _yogaNode; - std::atomic _flexWrap; - std::atomic _flexDirection; - std::atomic _direction; - std::atomic _justifyContent; - std::atomic _alignItems; - std::atomic _positionType; - std::atomic _position; - std::atomic _margin; - std::atomic _padding; - std::atomic _border; - std::atomic _aspectRatio; -#endif -} - -@dynamic width, height, minWidth, maxWidth, minHeight, maxHeight; -@dynamic preferredSize, minSize, maxSize, preferredLayoutSize, minLayoutSize, maxLayoutSize; - -#pragma mark - Lifecycle - -- (instancetype)initWithDelegate:(id)delegate -{ - self = [self init]; - if (self) { - _delegate = delegate; - } - return self; -} - -- (instancetype)init -{ - self = [super init]; - if (self) { - _size = ASLayoutElementSizeMake(); - } - return self; -} - -ASSynthesizeLockingMethodsWithMutex(__instanceLock__) - -#pragma mark - ASLayoutElementStyleSize - -- (ASLayoutElementSize)size -{ - return _size.load(); -} - -- (void)setSize:(ASLayoutElementSize)size -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize = size; - }); - // No CallDelegate method as ASLayoutElementSize is currently internal. -} - -#pragma mark - ASLayoutElementStyleSizeForwarding - -- (ASDimension)width -{ - return _size.load().width; -} - -- (void)setWidth:(ASDimension)width -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.width = width; - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleWidthProperty); -} - -- (ASDimension)height -{ - return _size.load().height; -} - -- (void)setHeight:(ASDimension)height -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.height = height; - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleHeightProperty); -} - -- (ASDimension)minWidth -{ - return _size.load().minWidth; -} - -- (void)setMinWidth:(ASDimension)minWidth -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.minWidth = minWidth; - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinWidthProperty); -} - -- (ASDimension)maxWidth -{ - return _size.load().maxWidth; -} - -- (void)setMaxWidth:(ASDimension)maxWidth -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.maxWidth = maxWidth; - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxWidthProperty); -} - -- (ASDimension)minHeight -{ - return _size.load().minHeight; -} - -- (void)setMinHeight:(ASDimension)minHeight -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.minHeight = minHeight; - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinHeightProperty); -} - -- (ASDimension)maxHeight -{ - return _size.load().maxHeight; -} - -- (void)setMaxHeight:(ASDimension)maxHeight -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.maxHeight = maxHeight; - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxHeightProperty); -} - - -#pragma mark - ASLayoutElementStyleSizeHelpers - -- (void)setPreferredSize:(CGSize)preferredSize -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.width = ASDimensionMakeWithPoints(preferredSize.width); - newSize.height = ASDimensionMakeWithPoints(preferredSize.height); - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleWidthProperty); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleHeightProperty); -} - -- (CGSize)preferredSize -{ - ASLayoutElementSize size = _size.load(); - if (size.width.unit == ASDimensionUnitFraction) { - NSCAssert(NO, @"Cannot get preferredSize of element with fractional width. Width: %@.", NSStringFromASDimension(size.width)); - return CGSizeZero; - } - - if (size.height.unit == ASDimensionUnitFraction) { - NSCAssert(NO, @"Cannot get preferredSize of element with fractional height. Height: %@.", NSStringFromASDimension(size.height)); - return CGSizeZero; - } - - return CGSizeMake(size.width.value, size.height.value); -} - -- (void)setMinSize:(CGSize)minSize -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.minWidth = ASDimensionMakeWithPoints(minSize.width); - newSize.minHeight = ASDimensionMakeWithPoints(minSize.height); - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinWidthProperty); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinHeightProperty); -} - -- (void)setMaxSize:(CGSize)maxSize -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.maxWidth = ASDimensionMakeWithPoints(maxSize.width); - newSize.maxHeight = ASDimensionMakeWithPoints(maxSize.height); - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxWidthProperty); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxHeightProperty); -} - -- (ASLayoutSize)preferredLayoutSize -{ - ASLayoutElementSize size = _size.load(); - return ASLayoutSizeMake(size.width, size.height); -} - -- (void)setPreferredLayoutSize:(ASLayoutSize)preferredLayoutSize -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.width = preferredLayoutSize.width; - newSize.height = preferredLayoutSize.height; - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleWidthProperty); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleHeightProperty); -} - -- (ASLayoutSize)minLayoutSize -{ - ASLayoutElementSize size = _size.load(); - return ASLayoutSizeMake(size.minWidth, size.minHeight); -} - -- (void)setMinLayoutSize:(ASLayoutSize)minLayoutSize -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.minWidth = minLayoutSize.width; - newSize.minHeight = minLayoutSize.height; - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinWidthProperty); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinHeightProperty); -} - -- (ASLayoutSize)maxLayoutSize -{ - ASLayoutElementSize size = _size.load(); - return ASLayoutSizeMake(size.maxWidth, size.maxHeight); -} - -- (void)setMaxLayoutSize:(ASLayoutSize)maxLayoutSize -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.maxWidth = maxLayoutSize.width; - newSize.maxHeight = maxLayoutSize.height; - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxWidthProperty); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxHeightProperty); -} - -#pragma mark - ASStackLayoutElement - -- (void)setSpacingBefore:(CGFloat)spacingBefore -{ - _spacingBefore.store(spacingBefore); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleSpacingBeforeProperty); -} - -- (CGFloat)spacingBefore -{ - return _spacingBefore.load(); -} - -- (void)setSpacingAfter:(CGFloat)spacingAfter -{ - _spacingAfter.store(spacingAfter); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleSpacingAfterProperty); -} - -- (CGFloat)spacingAfter -{ - return _spacingAfter.load(); -} - -- (void)setFlexGrow:(CGFloat)flexGrow -{ - _flexGrow.store(flexGrow); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleFlexGrowProperty); -} - -- (CGFloat)flexGrow -{ - return _flexGrow.load(); -} - -- (void)setFlexShrink:(CGFloat)flexShrink -{ - _flexShrink.store(flexShrink); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleFlexShrinkProperty); -} - -- (CGFloat)flexShrink -{ - return _flexShrink.load(); -} - -- (void)setFlexBasis:(ASDimension)flexBasis -{ - _flexBasis.store(flexBasis); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleFlexBasisProperty); -} - -- (ASDimension)flexBasis -{ - return _flexBasis.load(); -} - -- (void)setAlignSelf:(ASStackLayoutAlignSelf)alignSelf -{ - _alignSelf.store(alignSelf); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleAlignSelfProperty); -} - -- (ASStackLayoutAlignSelf)alignSelf -{ - return _alignSelf.load(); -} - -- (void)setAscender:(CGFloat)ascender -{ - _ascender.store(ascender); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleAscenderProperty); -} - -- (CGFloat)ascender -{ - return _ascender.load(); -} - -- (void)setDescender:(CGFloat)descender -{ - _descender.store(descender); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleDescenderProperty); -} - -- (CGFloat)descender -{ - return _descender.load(); -} - -#pragma mark - ASAbsoluteLayoutElement - -- (void)setLayoutPosition:(CGPoint)layoutPosition -{ - _layoutPosition.store(layoutPosition); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleLayoutPositionProperty); -} - -- (CGPoint)layoutPosition -{ - return _layoutPosition.load(); -} - -#pragma mark - Extensions - -- (void)setLayoutOptionExtensionBool:(BOOL)value atIndex:(int)idx -{ - NSCAssert(idx < kMaxLayoutElementBoolExtensions, @"Setting index outside of max bool extensions space"); - - ASDN::MutexLocker l(__instanceLock__); - _extensions.boolExtensions[idx] = value; -} - -- (BOOL)layoutOptionExtensionBoolAtIndex:(int)idx\ -{ - NSCAssert(idx < kMaxLayoutElementBoolExtensions, @"Accessing index outside of max bool extensions space"); - - ASDN::MutexLocker l(__instanceLock__); - return _extensions.boolExtensions[idx]; -} - -- (void)setLayoutOptionExtensionInteger:(NSInteger)value atIndex:(int)idx -{ - NSCAssert(idx < kMaxLayoutElementStateIntegerExtensions, @"Setting index outside of max integer extensions space"); - - ASDN::MutexLocker l(__instanceLock__); - _extensions.integerExtensions[idx] = value; -} - -- (NSInteger)layoutOptionExtensionIntegerAtIndex:(int)idx -{ - NSCAssert(idx < kMaxLayoutElementStateIntegerExtensions, @"Accessing index outside of max integer extensions space"); - - ASDN::MutexLocker l(__instanceLock__); - return _extensions.integerExtensions[idx]; -} - -- (void)setLayoutOptionExtensionEdgeInsets:(UIEdgeInsets)value atIndex:(int)idx -{ - NSCAssert(idx < kMaxLayoutElementStateEdgeInsetExtensions, @"Setting index outside of max edge insets extensions space"); - - ASDN::MutexLocker l(__instanceLock__); - _extensions.edgeInsetsExtensions[idx] = value; -} - -- (UIEdgeInsets)layoutOptionExtensionEdgeInsetsAtIndex:(int)idx -{ - NSCAssert(idx < kMaxLayoutElementStateEdgeInsetExtensions, @"Accessing index outside of max edge insets extensions space"); - - ASDN::MutexLocker l(__instanceLock__); - return _extensions.edgeInsetsExtensions[idx]; -} - -#pragma mark - Debugging - -- (NSString *)description -{ - return ASObjectDescriptionMake(self, [self propertiesForDescription]); -} - -- (NSMutableArray *)propertiesForDescription -{ - NSMutableArray *result = [NSMutableArray array]; - - if ((self.minLayoutSize.width.unit != ASDimensionUnitAuto || - self.minLayoutSize.height.unit != ASDimensionUnitAuto)) { - [result addObject:@{ @"minLayoutSize" : NSStringFromASLayoutSize(self.minLayoutSize) }]; - } - - if ((self.preferredLayoutSize.width.unit != ASDimensionUnitAuto || - self.preferredLayoutSize.height.unit != ASDimensionUnitAuto)) { - [result addObject:@{ @"preferredSize" : NSStringFromASLayoutSize(self.preferredLayoutSize) }]; - } - - if ((self.maxLayoutSize.width.unit != ASDimensionUnitAuto || - self.maxLayoutSize.height.unit != ASDimensionUnitAuto)) { - [result addObject:@{ @"maxLayoutSize" : NSStringFromASLayoutSize(self.maxLayoutSize) }]; - } - - if (self.alignSelf != ASStackLayoutAlignSelfAuto) { - [result addObject:@{ @"alignSelf" : [@[@"ASStackLayoutAlignSelfAuto", - @"ASStackLayoutAlignSelfStart", - @"ASStackLayoutAlignSelfEnd", - @"ASStackLayoutAlignSelfCenter", - @"ASStackLayoutAlignSelfStretch"] objectAtIndex:self.alignSelf] }]; - } - - if (self.ascender != 0) { - [result addObject:@{ @"ascender" : @(self.ascender) }]; - } - - if (self.descender != 0) { - [result addObject:@{ @"descender" : @(self.descender) }]; - } - - if (ASDimensionEqualToDimension(self.flexBasis, ASDimensionAuto) == NO) { - [result addObject:@{ @"flexBasis" : NSStringFromASDimension(self.flexBasis) }]; - } - - if (self.flexGrow != 0) { - [result addObject:@{ @"flexGrow" : @(self.flexGrow) }]; - } - - if (self.flexShrink != 0) { - [result addObject:@{ @"flexShrink" : @(self.flexShrink) }]; - } - - if (self.spacingAfter != 0) { - [result addObject:@{ @"spacingAfter" : @(self.spacingAfter) }]; - } - - if (self.spacingBefore != 0) { - [result addObject:@{ @"spacingBefore" : @(self.spacingBefore) }]; - } - - if (CGPointEqualToPoint(self.layoutPosition, CGPointZero) == NO) { - [result addObject:@{ @"layoutPosition" : [NSValue valueWithCGPoint:self.layoutPosition] }]; - } - - return result; -} - -- (void)propertyDidChange:(NSString *)propertyName -{ -#if YOGA - /* TODO(appleguy): STYLE SETTER METHODS LEFT TO IMPLEMENT - void YGNodeStyleSetOverflow(YGNodeRef node, YGOverflow overflow); - void YGNodeStyleSetFlex(YGNodeRef node, float flex); - */ - - if (_yogaNode == NULL) { - return; - } - // Because the NSStrings used to identify each property are const, use efficient pointer comparison. - if (propertyName == ASLayoutElementStyleWidthProperty) { - YGNODE_STYLE_SET_DIMENSION(_yogaNode, Width, self.width); - } - else if (propertyName == ASLayoutElementStyleMinWidthProperty) { - YGNODE_STYLE_SET_DIMENSION(_yogaNode, MinWidth, self.minWidth); - } - else if (propertyName == ASLayoutElementStyleMaxWidthProperty) { - YGNODE_STYLE_SET_DIMENSION(_yogaNode, MaxWidth, self.maxWidth); - } - else if (propertyName == ASLayoutElementStyleHeightProperty) { - YGNODE_STYLE_SET_DIMENSION(_yogaNode, Height, self.height); - } - else if (propertyName == ASLayoutElementStyleMinHeightProperty) { - YGNODE_STYLE_SET_DIMENSION(_yogaNode, MinHeight, self.minHeight); - } - else if (propertyName == ASLayoutElementStyleMaxHeightProperty) { - YGNODE_STYLE_SET_DIMENSION(_yogaNode, MaxHeight, self.maxHeight); - } - else if (propertyName == ASLayoutElementStyleFlexGrowProperty) { - YGNodeStyleSetFlexGrow(_yogaNode, self.flexGrow); - } - else if (propertyName == ASLayoutElementStyleFlexShrinkProperty) { - YGNodeStyleSetFlexShrink(_yogaNode, self.flexShrink); - } - else if (propertyName == ASLayoutElementStyleFlexBasisProperty) { - YGNODE_STYLE_SET_DIMENSION(_yogaNode, FlexBasis, self.flexBasis); - } - else if (propertyName == ASLayoutElementStyleAlignSelfProperty) { - YGNodeStyleSetAlignSelf(_yogaNode, yogaAlignSelf(self.alignSelf)); - } - else if (propertyName == ASYogaFlexWrapProperty) { - YGNodeStyleSetFlexWrap(_yogaNode, self.flexWrap); - } - else if (propertyName == ASYogaFlexDirectionProperty) { - YGNodeStyleSetFlexDirection(_yogaNode, yogaFlexDirection(self.flexDirection)); - } - else if (propertyName == ASYogaDirectionProperty) { - YGNodeStyleSetDirection(_yogaNode, self.direction); - } - else if (propertyName == ASYogaJustifyContentProperty) { - YGNodeStyleSetJustifyContent(_yogaNode, yogaJustifyContent(self.justifyContent)); - } - else if (propertyName == ASYogaAlignItemsProperty) { - ASStackLayoutAlignItems alignItems = self.alignItems; - if (alignItems != ASStackLayoutAlignItemsNotSet) { - YGNodeStyleSetAlignItems(_yogaNode, yogaAlignItems(alignItems)); - } - } - else if (propertyName == ASYogaPositionTypeProperty) { - YGNodeStyleSetPositionType(_yogaNode, self.positionType); - } - else if (propertyName == ASYogaPositionProperty) { - ASEdgeInsets position = self.position; - YGEdge edge = YGEdgeLeft; - for (int i = 0; i < YGEdgeAll + 1; ++i) { - YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(_yogaNode, Position, dimensionForEdgeWithEdgeInsets(edge, position), edge); - edge = (YGEdge)(edge + 1); - } - } - else if (propertyName == ASYogaMarginProperty) { - ASEdgeInsets margin = self.margin; - YGEdge edge = YGEdgeLeft; - for (int i = 0; i < YGEdgeAll + 1; ++i) { - YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(_yogaNode, Margin, dimensionForEdgeWithEdgeInsets(edge, margin), edge); - edge = (YGEdge)(edge + 1); - } - } - else if (propertyName == ASYogaPaddingProperty) { - ASEdgeInsets padding = self.padding; - YGEdge edge = YGEdgeLeft; - for (int i = 0; i < YGEdgeAll + 1; ++i) { - YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(_yogaNode, Padding, dimensionForEdgeWithEdgeInsets(edge, padding), edge); - edge = (YGEdge)(edge + 1); - } - } - else if (propertyName == ASYogaBorderProperty) { - ASEdgeInsets border = self.border; - YGEdge edge = YGEdgeLeft; - for (int i = 0; i < YGEdgeAll + 1; ++i) { - YGNODE_STYLE_SET_FLOAT_WITH_EDGE(_yogaNode, Border, dimensionForEdgeWithEdgeInsets(edge, border), edge); - edge = (YGEdge)(edge + 1); - } - } - else if (propertyName == ASYogaAspectRatioProperty) { - CGFloat aspectRatio = self.aspectRatio; - if (aspectRatio > FLT_EPSILON && aspectRatio < CGFLOAT_MAX / 2.0) { - YGNodeStyleSetAspectRatio(_yogaNode, aspectRatio); - } - } -#endif -} - -#pragma mark - Yoga Flexbox Properties - -#if YOGA - -+ (void)initialize -{ - [super initialize]; - YGConfigSetPointScaleFactor(YGConfigGetDefault(), ASScreenScale()); - // Yoga recommends using Web Defaults for all new projects. This will be enabled for Texture very soon. - //YGConfigSetUseWebDefaults(YGConfigGetDefault(), true); -} - -- (YGNodeRef)yogaNode -{ - return _yogaNode; -} - -- (YGNodeRef)yogaNodeCreateIfNeeded -{ - if (_yogaNode == NULL) { - _yogaNode = YGNodeNew(); - } - return _yogaNode; -} - -- (void)destroyYogaNode -{ - if (_yogaNode != NULL) { - // Release the __bridge_retained Context object. - ASLayoutElementYogaUpdateMeasureFunc(_yogaNode, nil); - YGNodeFree(_yogaNode); - _yogaNode = NULL; - } -} - -- (void)dealloc -{ - [self destroyYogaNode]; -} - -- (YGWrap)flexWrap { return _flexWrap.load(); } -- (ASStackLayoutDirection)flexDirection { return _flexDirection.load(); } -- (YGDirection)direction { return _direction.load(); } -- (ASStackLayoutJustifyContent)justifyContent { return _justifyContent.load(); } -- (ASStackLayoutAlignItems)alignItems { return _alignItems.load(); } -- (YGPositionType)positionType { return _positionType.load(); } -- (ASEdgeInsets)position { return _position.load(); } -- (ASEdgeInsets)margin { return _margin.load(); } -- (ASEdgeInsets)padding { return _padding.load(); } -- (ASEdgeInsets)border { return _border.load(); } -- (CGFloat)aspectRatio { return _aspectRatio.load(); } - -- (void)setFlexWrap:(YGWrap)flexWrap { - _flexWrap.store(flexWrap); - ASLayoutElementStyleCallDelegate(ASYogaFlexWrapProperty); -} -- (void)setFlexDirection:(ASStackLayoutDirection)flexDirection { - _flexDirection.store(flexDirection); - ASLayoutElementStyleCallDelegate(ASYogaFlexDirectionProperty); -} -- (void)setDirection:(YGDirection)direction { - _direction.store(direction); - ASLayoutElementStyleCallDelegate(ASYogaDirectionProperty); -} -- (void)setJustifyContent:(ASStackLayoutJustifyContent)justify { - _justifyContent.store(justify); - ASLayoutElementStyleCallDelegate(ASYogaJustifyContentProperty); -} -- (void)setAlignItems:(ASStackLayoutAlignItems)alignItems { - _alignItems.store(alignItems); - ASLayoutElementStyleCallDelegate(ASYogaAlignItemsProperty); -} -- (void)setPositionType:(YGPositionType)positionType { - _positionType.store(positionType); - ASLayoutElementStyleCallDelegate(ASYogaPositionTypeProperty); -} -- (void)setPosition:(ASEdgeInsets)position { - _position.store(position); - ASLayoutElementStyleCallDelegate(ASYogaPositionProperty); -} -- (void)setMargin:(ASEdgeInsets)margin { - _margin.store(margin); - ASLayoutElementStyleCallDelegate(ASYogaMarginProperty); -} -- (void)setPadding:(ASEdgeInsets)padding { - _padding.store(padding); - ASLayoutElementStyleCallDelegate(ASYogaPaddingProperty); -} -- (void)setBorder:(ASEdgeInsets)border { - _border.store(border); - ASLayoutElementStyleCallDelegate(ASYogaBorderProperty); -} -- (void)setAspectRatio:(CGFloat)aspectRatio { - _aspectRatio.store(aspectRatio); - ASLayoutElementStyleCallDelegate(ASYogaAspectRatioProperty); -} - -#endif /* YOGA */ - -@end diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASOverlayLayoutSpec.h b/submodules/AsyncDisplayKit/Source/Layout/ASOverlayLayoutSpec.h deleted file mode 100644 index a29a72b750..0000000000 --- a/submodules/AsyncDisplayKit/Source/Layout/ASOverlayLayoutSpec.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// ASOverlayLayoutSpec.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - This layout spec lays out a single layoutElement child and then overlays a layoutElement object on top of it streched to its size - */ -@interface ASOverlayLayoutSpec : ASLayoutSpec - -/** - * Overlay layoutElement of this layout spec - */ -@property (nonatomic) id overlay; - -/** - * Creates and returns an ASOverlayLayoutSpec object with a given child and an layoutElement that act as overlay. - * - * @param child A child that is laid out to determine the size of this spec. - * @param overlay A layoutElement object that is laid out over the child. - */ -+ (instancetype)overlayLayoutSpecWithChild:(id)child overlay:(id)overlay NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASOverlayLayoutSpec.mm b/submodules/AsyncDisplayKit/Source/Layout/ASOverlayLayoutSpec.mm deleted file mode 100644 index 27ffbeb8c7..0000000000 --- a/submodules/AsyncDisplayKit/Source/Layout/ASOverlayLayoutSpec.mm +++ /dev/null @@ -1,88 +0,0 @@ -// -// ASOverlayLayoutSpec.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import -#import - -static NSUInteger const kUnderlayChildIndex = 0; -static NSUInteger const kOverlayChildIndex = 1; - -@implementation ASOverlayLayoutSpec - -#pragma mark - Class - -+ (instancetype)overlayLayoutSpecWithChild:(id)child overlay:(id)overlay NS_RETURNS_RETAINED -{ - return [[self alloc] initWithChild:child overlay:overlay]; -} - -#pragma mark - Lifecycle - -- (instancetype)initWithChild:(id)child overlay:(id)overlay -{ - if (!(self = [super init])) { - return nil; - } - self.child = child; - self.overlay = overlay; - return self; -} - -#pragma mark - Setter / Getter - -- (void)setChild:(id)child -{ - ASDisplayNodeAssertNotNil(child, @"Child that will be overlayed on shouldn't be nil"); - [super setChild:child atIndex:kUnderlayChildIndex]; -} - -- (id)child -{ - return [super childAtIndex:kUnderlayChildIndex]; -} - -- (void)setOverlay:(id)overlay -{ - ASDisplayNodeAssertNotNil(overlay, @"Overlay cannot be nil"); - [super setChild:overlay atIndex:kOverlayChildIndex]; -} - -- (id)overlay -{ - return [super childAtIndex:kOverlayChildIndex]; -} - -#pragma mark - ASLayoutSpec - -/** - First layout the contents, then fit the overlay on top of it. - */ -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize - restrictedToSize:(ASLayoutElementSize)size - relativeToParentSize:(CGSize)parentSize -{ - ASLayout *contentsLayout = [self.child layoutThatFits:constrainedSize parentSize:parentSize]; - contentsLayout.position = CGPointZero; - ASLayout *rawSublayouts[2]; - int i = 0; - rawSublayouts[i++] = contentsLayout; - if (self.overlay) { - ASLayout *overlayLayout = [self.overlay layoutThatFits:ASSizeRangeMake(contentsLayout.size) - parentSize:contentsLayout.size]; - overlayLayout.position = CGPointZero; - rawSublayouts[i++] = overlayLayout; - } - - const auto sublayouts = [NSArray arrayByTransferring:rawSublayouts count:i]; - return [ASLayout layoutWithLayoutElement:self size:contentsLayout.size sublayouts:sublayouts]; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASRatioLayoutSpec.h b/submodules/AsyncDisplayKit/Source/Layout/ASRatioLayoutSpec.h deleted file mode 100644 index b70e6fe7fb..0000000000 --- a/submodules/AsyncDisplayKit/Source/Layout/ASRatioLayoutSpec.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// ASRatioLayoutSpec.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@protocol ASLayoutElement; - -/** - Ratio layout spec - For when the content should respect a certain inherent ratio but can be scaled (think photos or videos) - The ratio passed is the ratio of height / width you expect - - For a ratio 0.5, the spec will have a flat rectangle shape - _ _ _ _ - | | - |_ _ _ _| - - For a ratio 2.0, the spec will be twice as tall as it is wide - _ _ - | | - | | - | | - |_ _| - - **/ -@interface ASRatioLayoutSpec : ASLayoutSpec - -@property (nonatomic) CGFloat ratio; - -+ (instancetype)ratioLayoutSpecWithRatio:(CGFloat)ratio child:(id)child NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASRatioLayoutSpec.mm b/submodules/AsyncDisplayKit/Source/Layout/ASRatioLayoutSpec.mm deleted file mode 100644 index 1580b2a60c..0000000000 --- a/submodules/AsyncDisplayKit/Source/Layout/ASRatioLayoutSpec.mm +++ /dev/null @@ -1,103 +0,0 @@ -// -// ASRatioLayoutSpec.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import -#import - -#import - -#import -#import - -#pragma mark - ASRatioLayoutSpec - -@implementation ASRatioLayoutSpec -{ - CGFloat _ratio; -} - -#pragma mark - Lifecycle - -+ (instancetype)ratioLayoutSpecWithRatio:(CGFloat)ratio child:(id)child NS_RETURNS_RETAINED -{ - return [[self alloc] initWithRatio:ratio child:child]; -} - -- (instancetype)initWithRatio:(CGFloat)ratio child:(id)child; -{ - if (!(self = [super init])) { - return nil; - } - - ASDisplayNodeAssertNotNil(child, @"Child cannot be nil"); - ASDisplayNodeAssert(ratio > 0, @"Ratio should be strictly positive, but received %f", ratio); - _ratio = ratio; - self.child = child; - - return self; -} - -#pragma mark - Setter / Getter - -- (void)setRatio:(CGFloat)ratio -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - _ratio = ratio; -} - -#pragma mark - ASLayoutElement - -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize -{ - std::vector sizeOptions; - - if (ASPointsValidForSize(constrainedSize.max.width)) { - sizeOptions.push_back(ASSizeRangeClamp(constrainedSize, { - constrainedSize.max.width, - ASFloorPixelValue(_ratio * constrainedSize.max.width) - })); - } - - if (ASPointsValidForSize(constrainedSize.max.height)) { - sizeOptions.push_back(ASSizeRangeClamp(constrainedSize, { - ASFloorPixelValue(constrainedSize.max.height / _ratio), - constrainedSize.max.height - })); - } - - // Choose the size closest to the desired ratio. - const auto &bestSize = std::max_element(sizeOptions.begin(), sizeOptions.end(), [&](const CGSize &a, const CGSize &b){ - return std::fabs((a.height / a.width) - _ratio) > std::fabs((b.height / b.width) - _ratio); - }); - - // If there is no max size in *either* dimension, we can't apply the ratio, so just pass our size range through. - const ASSizeRange childRange = (bestSize == sizeOptions.end()) ? constrainedSize : ASSizeRangeIntersect(constrainedSize, ASSizeRangeMake(*bestSize, *bestSize)); - const CGSize parentSize = (bestSize == sizeOptions.end()) ? ASLayoutElementParentSizeUndefined : *bestSize; - ASLayout *sublayout = [self.child layoutThatFits:childRange parentSize:parentSize]; - sublayout.position = CGPointZero; - return [ASLayout layoutWithLayoutElement:self size:sublayout.size sublayouts:@[sublayout]]; -} - -@end - -#pragma mark - ASRatioLayoutSpec (Debugging) - -@implementation ASRatioLayoutSpec (Debugging) - -#pragma mark - ASLayoutElementAsciiArtProtocol - -- (NSString *)asciiArtName -{ - return [NSString stringWithFormat:@"%@ (%.1f)", NSStringFromClass([self class]), self.ratio]; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASRelativeLayoutSpec.h b/submodules/AsyncDisplayKit/Source/Layout/ASRelativeLayoutSpec.h deleted file mode 100644 index 9b260d9ccc..0000000000 --- a/submodules/AsyncDisplayKit/Source/Layout/ASRelativeLayoutSpec.h +++ /dev/null @@ -1,87 +0,0 @@ -// -// ASRelativeLayoutSpec.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -/** - * How the child is positioned within the spec. - * - * The default option will position the child at point 0. - * Swift: use [] for the default behavior. - */ -typedef NS_ENUM(NSUInteger, ASRelativeLayoutSpecPosition) { - /** The child is positioned at point 0 */ - ASRelativeLayoutSpecPositionNone = 0, - /** The child is positioned at point 0 relatively to the layout axis (ie left / top most) */ - ASRelativeLayoutSpecPositionStart = 1, - /** The child is centered along the specified axis */ - ASRelativeLayoutSpecPositionCenter = 2, - /** The child is positioned at the maximum point of the layout axis (ie right / bottom most) */ - ASRelativeLayoutSpecPositionEnd = 3, -}; - -/** - * How much space the spec will take up. - * - * The default option will allow the spec to take up the maximum size possible. - * Swift: use [] for the default behavior. - */ -typedef NS_OPTIONS(NSUInteger, ASRelativeLayoutSpecSizingOption) { - /** The spec will take up the maximum size possible */ - ASRelativeLayoutSpecSizingOptionDefault, - /** The spec will take up the minimum size possible along the X axis */ - ASRelativeLayoutSpecSizingOptionMinimumWidth = 1 << 0, - /** The spec will take up the minimum size possible along the Y axis */ - ASRelativeLayoutSpecSizingOptionMinimumHeight = 1 << 1, - /** Convenience option to take up the minimum size along both the X and Y axis */ - ASRelativeLayoutSpecSizingOptionMinimumSize = ASRelativeLayoutSpecSizingOptionMinimumWidth | ASRelativeLayoutSpecSizingOptionMinimumHeight, -}; - -NS_ASSUME_NONNULL_BEGIN - -/** Lays out a single layoutElement child and positions it within the layout bounds according to vertical and horizontal positional specifiers. - * Can position the child at any of the 4 corners, or the middle of any of the 4 edges, as well as the center - similar to "9-part" image areas. - */ -@interface ASRelativeLayoutSpec : ASLayoutSpec - -// You may create a spec with alloc / init, then set any non-default properties; or use a convenience initialize that accepts all properties. -@property (nonatomic) ASRelativeLayoutSpecPosition horizontalPosition; -@property (nonatomic) ASRelativeLayoutSpecPosition verticalPosition; -@property (nonatomic) ASRelativeLayoutSpecSizingOption sizingOption; - -/*! - * @discussion convenience constructor for a ASRelativeLayoutSpec - * @param horizontalPosition how to position the item on the horizontal (x) axis - * @param verticalPosition how to position the item on the vertical (y) axis - * @param sizingOption how much size to take up - * @param child the child to layout - * @return a configured ASRelativeLayoutSpec - */ -+ (instancetype)relativePositionLayoutSpecWithHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition - verticalPosition:(ASRelativeLayoutSpecPosition)verticalPosition - sizingOption:(ASRelativeLayoutSpecSizingOption)sizingOption - child:(id)child NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -/*! - * @discussion convenience initializer for a ASRelativeLayoutSpec - * @param horizontalPosition how to position the item on the horizontal (x) axis - * @param verticalPosition how to position the item on the vertical (y) axis - * @param sizingOption how much size to take up - * @param child the child to layout - * @return a configured ASRelativeLayoutSpec - */ -- (instancetype)initWithHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition - verticalPosition:(ASRelativeLayoutSpecPosition)verticalPosition - sizingOption:(ASRelativeLayoutSpecSizingOption)sizingOption - child:(id)child; - -@end - -NS_ASSUME_NONNULL_END - diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASRelativeLayoutSpec.mm b/submodules/AsyncDisplayKit/Source/Layout/ASRelativeLayoutSpec.mm deleted file mode 100644 index f096ce51d4..0000000000 --- a/submodules/AsyncDisplayKit/Source/Layout/ASRelativeLayoutSpec.mm +++ /dev/null @@ -1,104 +0,0 @@ -// -// ASRelativeLayoutSpec.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -#import - -@implementation ASRelativeLayoutSpec - -- (instancetype)initWithHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition verticalPosition:(ASRelativeLayoutSpecPosition)verticalPosition sizingOption:(ASRelativeLayoutSpecSizingOption)sizingOption child:(id)child -{ - if (!(self = [super init])) { - return nil; - } - ASDisplayNodeAssertNotNil(child, @"Child cannot be nil"); - _horizontalPosition = horizontalPosition; - _verticalPosition = verticalPosition; - _sizingOption = sizingOption; - [self setChild:child]; - return self; -} - -+ (instancetype)relativePositionLayoutSpecWithHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition verticalPosition:(ASRelativeLayoutSpecPosition)verticalPosition sizingOption:(ASRelativeLayoutSpecSizingOption)sizingOption child:(id)child NS_RETURNS_RETAINED -{ - return [[self alloc] initWithHorizontalPosition:horizontalPosition verticalPosition:verticalPosition sizingOption:sizingOption child:child]; -} - -- (void)setHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - _horizontalPosition = horizontalPosition; -} - -- (void)setVerticalPosition:(ASRelativeLayoutSpecPosition)verticalPosition { - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - _verticalPosition = verticalPosition; -} - -- (void)setSizingOption:(ASRelativeLayoutSpecSizingOption)sizingOption -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - _sizingOption = sizingOption; -} - -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize -{ - // If we have a finite size in any direction, pass this so that the child can resolve percentages against it. - // Otherwise pass ASLayoutElementParentDimensionUndefined as the size will depend on the content - CGSize size = { - ASPointsValidForSize(constrainedSize.max.width) == NO ? ASLayoutElementParentDimensionUndefined : constrainedSize.max.width, - ASPointsValidForSize(constrainedSize.max.height) == NO ? ASLayoutElementParentDimensionUndefined : constrainedSize.max.height - }; - - // Layout the child - const CGSize minChildSize = { - (_horizontalPosition != ASRelativeLayoutSpecPositionNone) ? 0 : constrainedSize.min.width, - (_verticalPosition != ASRelativeLayoutSpecPositionNone) ? 0 : constrainedSize.min.height, - }; - ASLayout *sublayout = [self.child layoutThatFits:ASSizeRangeMake(minChildSize, constrainedSize.max) parentSize:size]; - - // If we have an undetermined height or width, use the child size to define the layout size - size = ASSizeRangeClamp(constrainedSize, { - isfinite(size.width) == NO ? sublayout.size.width : size.width, - isfinite(size.height) == NO ? sublayout.size.height : size.height - }); - - // If minimum size options are set, attempt to shrink the size to the size of the child - size = ASSizeRangeClamp(constrainedSize, { - MIN(size.width, (_sizingOption & ASRelativeLayoutSpecSizingOptionMinimumWidth) != 0 ? sublayout.size.width : size.width), - MIN(size.height, (_sizingOption & ASRelativeLayoutSpecSizingOptionMinimumHeight) != 0 ? sublayout.size.height : size.height) - }); - - // Compute the position for the child on each axis according to layout parameters - CGFloat xPosition = [self proportionOfAxisForAxisPosition:_horizontalPosition]; - CGFloat yPosition = [self proportionOfAxisForAxisPosition:_verticalPosition]; - - sublayout.position = { - ASRoundPixelValue((size.width - sublayout.size.width) * xPosition), - ASRoundPixelValue((size.height - sublayout.size.height) * yPosition) - }; - - return [ASLayout layoutWithLayoutElement:self size:size sublayouts:@[sublayout]]; -} - -- (CGFloat)proportionOfAxisForAxisPosition:(ASRelativeLayoutSpecPosition)position -{ - if (position == ASRelativeLayoutSpecPositionCenter) { - return 0.5f; - } else if (position == ASRelativeLayoutSpecPositionEnd) { - return 1.0f; - } else { - return 0.0f; - } -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASStackLayoutSpec.h b/submodules/AsyncDisplayKit/Source/Layout/ASStackLayoutSpec.h deleted file mode 100644 index 535758ac7b..0000000000 --- a/submodules/AsyncDisplayKit/Source/Layout/ASStackLayoutSpec.h +++ /dev/null @@ -1,133 +0,0 @@ -// -// ASStackLayoutSpec.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - A simple layout spec that stacks a list of children vertically or horizontally. - - - All children are initially laid out with the an infinite available size in the stacking direction. - - In the other direction, this spec's constraint is passed. - - The children's sizes are summed in the stacking direction. - - If this sum is less than this spec's minimum size in stacking direction, children with flexGrow are flexed. - - If it is greater than this spec's maximum size in the stacking direction, children with flexShrink are flexed. - - If, even after flexing, the sum is still greater than this spec's maximum size in the stacking direction, - justifyContent determines how children are laid out. - - For example: - - - Suppose stacking direction is Vertical, min-width=100, max-width=300, min-height=200, max-height=500. - - All children are laid out with min-width=100, max-width=300, min-height=0, max-height=INFINITY. - - If the sum of the childrens' heights is less than 200, children with flexGrow are flexed larger. - - If the sum of the childrens' heights is greater than 500, children with flexShrink are flexed smaller. - Each child is shrunk by `((sum of heights) - 500)/(number of flexShrink-able children)`. - - If the sum of the childrens' heights is greater than 500 even after flexShrink-able children are flexed, - justifyContent determines how children are laid out. - */ -@interface ASStackLayoutSpec : ASLayoutSpec - -/** - Specifies the direction children are stacked in. If horizontalAlignment and verticalAlignment were set, - they will be resolved again, causing justifyContent and alignItems to be updated accordingly - */ -@property (nonatomic) ASStackLayoutDirection direction; -/** The amount of space between each child. */ -@property (nonatomic) CGFloat spacing; -/** - Specifies how children are aligned horizontally. Depends on the stack direction, setting the alignment causes either - justifyContent or alignItems to be updated. The alignment will remain valid after future direction changes. - Thus, it is preferred to those properties - */ -@property (nonatomic) ASHorizontalAlignment horizontalAlignment; -/** - Specifies how children are aligned vertically. Depends on the stack direction, setting the alignment causes either - justifyContent or alignItems to be updated. The alignment will remain valid after future direction changes. - Thus, it is preferred to those properties - */ -@property (nonatomic) ASVerticalAlignment verticalAlignment; -/** The amount of space between each child. Defaults to ASStackLayoutJustifyContentStart */ -@property (nonatomic) ASStackLayoutJustifyContent justifyContent; -/** Orientation of children along cross axis. Defaults to ASStackLayoutAlignItemsStretch */ -@property (nonatomic) ASStackLayoutAlignItems alignItems; -/** Whether children are stacked into a single or multiple lines. Defaults to single line (ASStackLayoutFlexWrapNoWrap) */ -@property (nonatomic) ASStackLayoutFlexWrap flexWrap; -/** Orientation of lines along cross axis if there are multiple lines. Defaults to ASStackLayoutAlignContentStart */ -@property (nonatomic) ASStackLayoutAlignContent alignContent; -/** If the stack spreads on multiple lines using flexWrap, the amount of space between lines. */ -@property (nonatomic) CGFloat lineSpacing; -/** Whether this stack can dispatch to other threads, regardless of which thread it's running on */ -@property (nonatomic, getter=isConcurrent) BOOL concurrent; - -- (instancetype)init; - -/** - @param direction The direction of the stack view (horizontal or vertical) - @param spacing The spacing between the children - @param justifyContent If no children are flexible, this describes how to fill any extra space - @param alignItems Orientation of the children along the cross axis - @param children ASLayoutElement children to be positioned. - */ -+ (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction - spacing:(CGFloat)spacing - justifyContent:(ASStackLayoutJustifyContent)justifyContent - alignItems:(ASStackLayoutAlignItems)alignItems - children:(NSArray> *)children NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -/** - @param direction The direction of the stack view (horizontal or vertical) - @param spacing The spacing between the children - @param justifyContent If no children are flexible, this describes how to fill any extra space - @param alignItems Orientation of the children along the cross axis - @param flexWrap Whether children are stacked into a single or multiple lines - @param alignContent Orientation of lines along cross axis if there are multiple lines - @param children ASLayoutElement children to be positioned. - */ -+ (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction - spacing:(CGFloat)spacing - justifyContent:(ASStackLayoutJustifyContent)justifyContent - alignItems:(ASStackLayoutAlignItems)alignItems - flexWrap:(ASStackLayoutFlexWrap)flexWrap - alignContent:(ASStackLayoutAlignContent)alignContent - children:(NSArray> *)children NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -/** - @param direction The direction of the stack view (horizontal or vertical) - @param spacing The spacing between the children - @param justifyContent If no children are flexible, this describes how to fill any extra space - @param alignItems Orientation of the children along the cross axis - @param flexWrap Whether children are stacked into a single or multiple lines - @param alignContent Orientation of lines along cross axis if there are multiple lines - @param lineSpacing The spacing between lines - @param children ASLayoutElement children to be positioned. - */ -+ (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction - spacing:(CGFloat)spacing - justifyContent:(ASStackLayoutJustifyContent)justifyContent - alignItems:(ASStackLayoutAlignItems)alignItems - flexWrap:(ASStackLayoutFlexWrap)flexWrap - alignContent:(ASStackLayoutAlignContent)alignContent - lineSpacing:(CGFloat)lineSpacing - children:(NSArray> *)children NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -/** - * @return A stack layout spec with direction of ASStackLayoutDirectionVertical - **/ -+ (instancetype)verticalStackLayoutSpec NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -/** - * @return A stack layout spec with direction of ASStackLayoutDirectionHorizontal - **/ -+ (instancetype)horizontalStackLayoutSpec NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASStackLayoutSpec.mm b/submodules/AsyncDisplayKit/Source/Layout/ASStackLayoutSpec.mm deleted file mode 100644 index c7c9f8dd21..0000000000 --- a/submodules/AsyncDisplayKit/Source/Layout/ASStackLayoutSpec.mm +++ /dev/null @@ -1,212 +0,0 @@ -// -// ASStackLayoutSpec.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import - -@implementation ASStackLayoutSpec - -- (instancetype)init -{ - return [self initWithDirection:ASStackLayoutDirectionHorizontal spacing:0.0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsStretch flexWrap:ASStackLayoutFlexWrapNoWrap alignContent:ASStackLayoutAlignContentStart lineSpacing:0.0 children:nil]; -} - -+ (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems children:(NSArray *)children NS_RETURNS_RETAINED -{ - return [[self alloc] initWithDirection:direction spacing:spacing justifyContent:justifyContent alignItems:alignItems flexWrap:ASStackLayoutFlexWrapNoWrap alignContent:ASStackLayoutAlignContentStart lineSpacing: 0.0 children:children]; -} - -+ (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems flexWrap:(ASStackLayoutFlexWrap)flexWrap alignContent:(ASStackLayoutAlignContent)alignContent children:(NSArray> *)children NS_RETURNS_RETAINED -{ - return [[self alloc] initWithDirection:direction spacing:spacing justifyContent:justifyContent alignItems:alignItems flexWrap:flexWrap alignContent:alignContent lineSpacing:0.0 children:children]; -} - -+ (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems flexWrap:(ASStackLayoutFlexWrap)flexWrap alignContent:(ASStackLayoutAlignContent)alignContent lineSpacing:(CGFloat)lineSpacing children:(NSArray> *)children NS_RETURNS_RETAINED -{ - return [[self alloc] initWithDirection:direction spacing:spacing justifyContent:justifyContent alignItems:alignItems flexWrap:flexWrap alignContent:alignContent lineSpacing:lineSpacing children:children]; -} - -+ (instancetype)verticalStackLayoutSpec NS_RETURNS_RETAINED -{ - ASStackLayoutSpec *stackLayoutSpec = [[self alloc] init]; - stackLayoutSpec.direction = ASStackLayoutDirectionVertical; - return stackLayoutSpec; -} - -+ (instancetype)horizontalStackLayoutSpec NS_RETURNS_RETAINED -{ - ASStackLayoutSpec *stackLayoutSpec = [[self alloc] init]; - stackLayoutSpec.direction = ASStackLayoutDirectionHorizontal; - return stackLayoutSpec; -} - -- (instancetype)initWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems flexWrap:(ASStackLayoutFlexWrap)flexWrap alignContent:(ASStackLayoutAlignContent)alignContent lineSpacing:(CGFloat)lineSpacing children:(NSArray *)children -{ - if (!(self = [super init])) { - return nil; - } - _direction = direction; - _spacing = spacing; - _horizontalAlignment = ASHorizontalAlignmentNone; - _verticalAlignment = ASVerticalAlignmentNone; - _alignItems = alignItems; - _justifyContent = justifyContent; - _flexWrap = flexWrap; - _alignContent = alignContent; - _lineSpacing = lineSpacing; - - [self setChildren:children]; - return self; -} - -- (void)setDirection:(ASStackLayoutDirection)direction -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - if (_direction != direction) { - _direction = direction; - [self resolveHorizontalAlignment]; - [self resolveVerticalAlignment]; - } -} - -- (void)setHorizontalAlignment:(ASHorizontalAlignment)horizontalAlignment -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - if (_horizontalAlignment != horizontalAlignment) { - _horizontalAlignment = horizontalAlignment; - [self resolveHorizontalAlignment]; - } -} - -- (void)setVerticalAlignment:(ASVerticalAlignment)verticalAlignment -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - if (_verticalAlignment != verticalAlignment) { - _verticalAlignment = verticalAlignment; - [self resolveVerticalAlignment]; - } -} - -- (void)setAlignItems:(ASStackLayoutAlignItems)alignItems -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - ASDisplayNodeAssert(_horizontalAlignment == ASHorizontalAlignmentNone, @"Cannot set this property directly because horizontalAlignment is being used"); - ASDisplayNodeAssert(_verticalAlignment == ASVerticalAlignmentNone, @"Cannot set this property directly because verticalAlignment is being used"); - _alignItems = alignItems; -} - -- (void)setJustifyContent:(ASStackLayoutJustifyContent)justifyContent -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - ASDisplayNodeAssert(_horizontalAlignment == ASHorizontalAlignmentNone, @"Cannot set this property directly because horizontalAlignment is being used"); - ASDisplayNodeAssert(_verticalAlignment == ASVerticalAlignmentNone, @"Cannot set this property directly because verticalAlignment is being used"); - _justifyContent = justifyContent; -} - -- (void)setSpacing:(CGFloat)spacing -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - _spacing = spacing; -} - -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize -{ - NSArray *children = self.children; - if (children.count == 0) { - return [ASLayout layoutWithLayoutElement:self size:constrainedSize.min]; - } - - as_activity_scope_verbose(as_activity_create("Calculate stack layout", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT)); - as_log_verbose(ASLayoutLog(), "Stack layout %@", self); - // Accessing the style and size property is pretty costly we create layout spec children we use to figure - // out the layout for each child - const auto stackChildren = AS::map(children, [&](const id child) -> ASStackLayoutSpecChild { - ASLayoutElementStyle *style = child.style; - return {child, style, style.size}; - }); - - const ASStackLayoutSpecStyle style = {.direction = _direction, .spacing = _spacing, .justifyContent = _justifyContent, .alignItems = _alignItems, .flexWrap = _flexWrap, .alignContent = _alignContent, .lineSpacing = _lineSpacing}; - - const auto unpositionedLayout = ASStackUnpositionedLayout::compute(stackChildren, style, constrainedSize, _concurrent); - const auto positionedLayout = ASStackPositionedLayout::compute(unpositionedLayout, style, constrainedSize); - - if (style.direction == ASStackLayoutDirectionVertical) { - self.style.ascender = stackChildren.front().style.ascender; - self.style.descender = stackChildren.back().style.descender; - } - - ASLayout *rawSublayouts[positionedLayout.items.size()]; - int i = 0; - for (const auto &item : positionedLayout.items) { - rawSublayouts[i++] = item.layout; - } - - const auto sublayouts = [NSArray arrayByTransferring:rawSublayouts count:i]; - return [ASLayout layoutWithLayoutElement:self size:positionedLayout.size sublayouts:sublayouts]; -} - -- (void)resolveHorizontalAlignment -{ - if (_direction == ASStackLayoutDirectionHorizontal) { - _justifyContent = justifyContent(_horizontalAlignment, _justifyContent); - } else { - _alignItems = alignment(_horizontalAlignment, _alignItems); - } -} - -- (void)resolveVerticalAlignment -{ - if (_direction == ASStackLayoutDirectionHorizontal) { - _alignItems = alignment(_verticalAlignment, _alignItems); - } else { - _justifyContent = justifyContent(_verticalAlignment, _justifyContent); - } -} - -- (NSMutableArray *)propertiesForDescription -{ - auto result = [super propertiesForDescription]; - - // Add our direction - switch (self.direction) { - case ASStackLayoutDirectionVertical: - [result insertObject:@{ (id)kCFNull: @"vertical" } atIndex:0]; - break; - case ASStackLayoutDirectionHorizontal: - [result insertObject:@{ (id)kCFNull: @"horizontal" } atIndex:0]; - break; - } - - return result; -} - -@end - -@implementation ASStackLayoutSpec (Debugging) - -#pragma mark - ASLayoutElementAsciiArtProtocol - -- (NSString *)asciiArtString -{ - return [ASLayoutSpec asciiArtStringForChildren:self.children parentName:[self asciiArtName] direction:self.direction]; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASYogaUtilities.h b/submodules/AsyncDisplayKit/Source/Layout/ASYogaUtilities.h deleted file mode 100644 index c3d1d8233c..0000000000 --- a/submodules/AsyncDisplayKit/Source/Layout/ASYogaUtilities.h +++ /dev/null @@ -1,78 +0,0 @@ -// -// ASYogaUtilities.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if YOGA /* YOGA */ - -#import -#import -#import - -// Should pass a string literal, not an NSString as the first argument to ASYogaLog -#define ASYogaLog(x, ...) as_log_verbose(ASLayoutLog(), x, ##__VA_ARGS__); - -@interface ASDisplayNode (YogaHelpers) - -+ (ASDisplayNode *)yogaNode; -+ (ASDisplayNode *)yogaSpacerNode; -+ (ASDisplayNode *)yogaVerticalStack; -+ (ASDisplayNode *)yogaHorizontalStack; - -@end - -// pre-order, depth-first -AS_EXTERN void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode *node, void(^block)(ASDisplayNode *node)); - -#pragma mark - Yoga Type Conversion Helpers - -AS_EXTERN YGAlign yogaAlignItems(ASStackLayoutAlignItems alignItems); -AS_EXTERN YGJustify yogaJustifyContent(ASStackLayoutJustifyContent justifyContent); -AS_EXTERN YGAlign yogaAlignSelf(ASStackLayoutAlignSelf alignSelf); -AS_EXTERN YGFlexDirection yogaFlexDirection(ASStackLayoutDirection direction); -AS_EXTERN float yogaFloatForCGFloat(CGFloat value); -AS_EXTERN float yogaDimensionToPoints(ASDimension dimension); -AS_EXTERN float yogaDimensionToPercent(ASDimension dimension); -AS_EXTERN ASDimension dimensionForEdgeWithEdgeInsets(YGEdge edge, ASEdgeInsets insets); - -AS_EXTERN void ASLayoutElementYogaUpdateMeasureFunc(YGNodeRef yogaNode, id layoutElement); -AS_EXTERN float ASLayoutElementYogaBaselineFunc(YGNodeRef yogaNode, const float width, const float height); -AS_EXTERN YGSize ASLayoutElementYogaMeasureFunc(YGNodeRef yogaNode, - float width, YGMeasureMode widthMode, - float height, YGMeasureMode heightMode); - -#pragma mark - Yoga Style Setter Helpers - -#define YGNODE_STYLE_SET_DIMENSION(yogaNode, property, dimension) \ - if (dimension.unit == ASDimensionUnitPoints) { \ - YGNodeStyleSet##property(yogaNode, yogaDimensionToPoints(dimension)); \ - } else if (dimension.unit == ASDimensionUnitFraction) { \ - YGNodeStyleSet##property##Percent(yogaNode, yogaDimensionToPercent(dimension)); \ - } else { \ - YGNodeStyleSet##property(yogaNode, YGUndefined); \ - }\ - -#define YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(yogaNode, property, dimension, edge) \ - if (dimension.unit == ASDimensionUnitPoints) { \ - YGNodeStyleSet##property(yogaNode, edge, yogaDimensionToPoints(dimension)); \ - } else if (dimension.unit == ASDimensionUnitFraction) { \ - YGNodeStyleSet##property##Percent(yogaNode, edge, yogaDimensionToPercent(dimension)); \ - } else { \ - YGNodeStyleSet##property(yogaNode, edge, YGUndefined); \ - } \ - -#define YGNODE_STYLE_SET_FLOAT_WITH_EDGE(yogaNode, property, dimension, edge) \ - if (dimension.unit == ASDimensionUnitPoints) { \ - YGNodeStyleSet##property(yogaNode, edge, yogaDimensionToPoints(dimension)); \ - } else if (dimension.unit == ASDimensionUnitFraction) { \ - ASDisplayNodeAssert(NO, @"Unexpected Fraction value in applying ##property## values to YGNode"); \ - } else { \ - YGNodeStyleSet##property(yogaNode, edge, YGUndefined); \ - } \ - -#endif /* YOGA */ diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASYogaUtilities.mm b/submodules/AsyncDisplayKit/Source/Layout/ASYogaUtilities.mm deleted file mode 100644 index f0602ed4bd..0000000000 --- a/submodules/AsyncDisplayKit/Source/Layout/ASYogaUtilities.mm +++ /dev/null @@ -1,240 +0,0 @@ -// -// ASYogaUtilities.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#if YOGA /* YOGA */ - -@implementation ASDisplayNode (YogaHelpers) - -+ (ASDisplayNode *)yogaNode -{ - ASDisplayNode *node = [[ASDisplayNode alloc] init]; - node.automaticallyManagesSubnodes = YES; - [node.style yogaNodeCreateIfNeeded]; - return node; -} - -+ (ASDisplayNode *)yogaSpacerNode -{ - ASDisplayNode *node = [ASDisplayNode yogaNode]; - node.style.flexGrow = 1.0f; - return node; -} - -+ (ASDisplayNode *)yogaVerticalStack -{ - ASDisplayNode *node = [self yogaNode]; - node.style.flexDirection = ASStackLayoutDirectionVertical; - return node; -} - -+ (ASDisplayNode *)yogaHorizontalStack -{ - ASDisplayNode *node = [self yogaNode]; - node.style.flexDirection = ASStackLayoutDirectionHorizontal; - return node; -} - -@end - -void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode *node, void(^block)(ASDisplayNode *node)) -{ - if (node == nil) { - return; - } - block(node); - // We use the accessor here despite the copy, because the block may modify the yoga tree e.g. - // replacing a node. - for (ASDisplayNode *child in node.yogaChildren) { - ASDisplayNodePerformBlockOnEveryYogaChild(child, block); - } -} - -#pragma mark - Yoga Type Conversion Helpers - -YGAlign yogaAlignItems(ASStackLayoutAlignItems alignItems) -{ - switch (alignItems) { - case ASStackLayoutAlignItemsNotSet: return YGAlignAuto; - case ASStackLayoutAlignItemsStart: return YGAlignFlexStart; - case ASStackLayoutAlignItemsEnd: return YGAlignFlexEnd; - case ASStackLayoutAlignItemsCenter: return YGAlignCenter; - case ASStackLayoutAlignItemsStretch: return YGAlignStretch; - case ASStackLayoutAlignItemsBaselineFirst: return YGAlignBaseline; - // FIXME: WARNING, Yoga does not currently support last-baseline item alignment. - case ASStackLayoutAlignItemsBaselineLast: return YGAlignBaseline; - } -} - -YGJustify yogaJustifyContent(ASStackLayoutJustifyContent justifyContent) -{ - switch (justifyContent) { - case ASStackLayoutJustifyContentStart: return YGJustifyFlexStart; - case ASStackLayoutJustifyContentCenter: return YGJustifyCenter; - case ASStackLayoutJustifyContentEnd: return YGJustifyFlexEnd; - case ASStackLayoutJustifyContentSpaceBetween: return YGJustifySpaceBetween; - case ASStackLayoutJustifyContentSpaceAround: return YGJustifySpaceAround; - } -} - -YGAlign yogaAlignSelf(ASStackLayoutAlignSelf alignSelf) -{ - switch (alignSelf) { - case ASStackLayoutAlignSelfStart: return YGAlignFlexStart; - case ASStackLayoutAlignSelfCenter: return YGAlignCenter; - case ASStackLayoutAlignSelfEnd: return YGAlignFlexEnd; - case ASStackLayoutAlignSelfStretch: return YGAlignStretch; - case ASStackLayoutAlignSelfAuto: return YGAlignAuto; - } -} - -YGFlexDirection yogaFlexDirection(ASStackLayoutDirection direction) -{ - return direction == ASStackLayoutDirectionVertical ? YGFlexDirectionColumn : YGFlexDirectionRow; -} - -float yogaFloatForCGFloat(CGFloat value) -{ - if (value < CGFLOAT_MAX / 2) { - return value; - } else { - return YGUndefined; - } -} - -float cgFloatForYogaFloat(float yogaFloat) -{ - return (yogaFloat == YGUndefined) ? CGFLOAT_MAX : yogaFloat; -} - -float yogaDimensionToPoints(ASDimension dimension) -{ - ASDisplayNodeCAssert(dimension.unit == ASDimensionUnitPoints, - @"Dimensions should not be type Fraction for this method: %f", dimension.value); - return yogaFloatForCGFloat(dimension.value); -} - -float yogaDimensionToPercent(ASDimension dimension) -{ - ASDisplayNodeCAssert(dimension.unit == ASDimensionUnitFraction, - @"Dimensions should not be type Points for this method: %f", dimension.value); - return 100.0 * yogaFloatForCGFloat(dimension.value); - -} - -ASDimension dimensionForEdgeWithEdgeInsets(YGEdge edge, ASEdgeInsets insets) -{ - switch (edge) { - case YGEdgeLeft: return insets.left; - case YGEdgeTop: return insets.top; - case YGEdgeRight: return insets.right; - case YGEdgeBottom: return insets.bottom; - case YGEdgeStart: return insets.start; - case YGEdgeEnd: return insets.end; - case YGEdgeHorizontal: return insets.horizontal; - case YGEdgeVertical: return insets.vertical; - case YGEdgeAll: return insets.all; - default: ASDisplayNodeCAssert(NO, @"YGEdge other than ASEdgeInsets is not supported."); - return ASDimensionAuto; - } -} - -void ASLayoutElementYogaUpdateMeasureFunc(YGNodeRef yogaNode, id layoutElement) -{ - if (yogaNode == NULL) { - return; - } - - BOOL shouldHaveMeasureFunc = [layoutElement implementsLayoutMethod]; - // How expensive is it to set a baselineFunc on all (leaf) nodes? - BOOL shouldHaveBaselineFunc = YES; - - if (layoutElement != nil) { - if (shouldHaveMeasureFunc || shouldHaveBaselineFunc) { - // Retain the Context object. This must be explicitly released with a - // __bridge_transfer - YGNodeFree() is not sufficient. - YGNodeSetContext(yogaNode, (__bridge_retained void *)layoutElement); - } - if (shouldHaveMeasureFunc) { - YGNodeSetMeasureFunc(yogaNode, &ASLayoutElementYogaMeasureFunc); - } - if (shouldHaveBaselineFunc) { - YGNodeSetBaselineFunc(yogaNode, &ASLayoutElementYogaBaselineFunc); - } - ASDisplayNodeCAssert(YGNodeGetContext(yogaNode) == (__bridge void *)layoutElement, - @"Yoga node context should contain layoutElement: %@", layoutElement); - } else { - // If we lack any of the conditions above, and currently have a measureFn/baselineFn/context, - // get rid of it. - // Release the __bridge_retained Context object. - __unused id element = (__bridge_transfer id)YGNodeGetContext(yogaNode); - YGNodeSetContext(yogaNode, NULL); - YGNodeSetMeasureFunc(yogaNode, NULL); - YGNodeSetBaselineFunc(yogaNode, NULL); - } -} - -float ASLayoutElementYogaBaselineFunc(YGNodeRef yogaNode, const float width, const float height) -{ - id layoutElement = (__bridge id)YGNodeGetContext(yogaNode); - ASDisplayNodeCAssert([layoutElement conformsToProtocol:@protocol(ASLayoutElement)], - @"Yoga context must be "); - - ASDisplayNode *displayNode = ASDynamicCast(layoutElement, ASDisplayNode); - - switch (displayNode.style.parentAlignStyle) { - case ASStackLayoutAlignItemsBaselineFirst: - return layoutElement.style.ascender; - case ASStackLayoutAlignItemsBaselineLast: - return height + layoutElement.style.descender; - default: - return 0; - } -} - -YGSize ASLayoutElementYogaMeasureFunc(YGNodeRef yogaNode, float width, YGMeasureMode widthMode, - float height, YGMeasureMode heightMode) -{ - id layoutElement = (__bridge id )YGNodeGetContext(yogaNode); - ASDisplayNodeCAssert([layoutElement conformsToProtocol:@protocol(ASLayoutElement)], @"Yoga context must be "); - - width = cgFloatForYogaFloat(width); - height = cgFloatForYogaFloat(height); - - ASSizeRange sizeRange; - sizeRange.min = CGSizeZero; - sizeRange.max = CGSizeMake(width, height); - if (widthMode == YGMeasureModeExactly) { - sizeRange.min.width = sizeRange.max.width; - } else { - // Mode is (YGMeasureModeAtMost | YGMeasureModeUndefined) - ASDimension minWidth = layoutElement.style.minWidth; - sizeRange.min.width = (minWidth.unit == ASDimensionUnitPoints ? yogaDimensionToPoints(minWidth) : 0.0); - } - if (heightMode == YGMeasureModeExactly) { - sizeRange.min.height = sizeRange.max.height; - } else { - // Mode is (YGMeasureModeAtMost | YGMeasureModeUndefined) - ASDimension minHeight = layoutElement.style.minHeight; - sizeRange.min.height = (minHeight.unit == ASDimensionUnitPoints ? yogaDimensionToPoints(minHeight) : 0.0); - } - - ASDisplayNodeCAssert(isnan(sizeRange.min.width) == NO && isnan(sizeRange.min.height) == NO, @"Yoga size range for measurement should not have NaN in minimum"); - if (isnan(sizeRange.max.width)) { - sizeRange.max.width = CGFLOAT_MAX; - } - if (isnan(sizeRange.max.height)) { - sizeRange.max.height = CGFLOAT_MAX; - } - - CGSize size = [[layoutElement layoutThatFits:sizeRange] size]; - return (YGSize){ .width = (float)size.width, .height = (float)size.height }; -} - -#endif /* YOGA */ diff --git a/submodules/AsyncDisplayKit/Source/Details/NSArray+Diffing.mm b/submodules/AsyncDisplayKit/Source/NSArray+Diffing.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/Details/NSArray+Diffing.mm rename to submodules/AsyncDisplayKit/Source/NSArray+Diffing.mm diff --git a/submodules/AsyncDisplayKit/Source/Details/NSIndexSet+ASHelpers.h b/submodules/AsyncDisplayKit/Source/NSIndexSet+ASHelpers.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/Details/NSIndexSet+ASHelpers.h rename to submodules/AsyncDisplayKit/Source/NSIndexSet+ASHelpers.h diff --git a/submodules/AsyncDisplayKit/Source/Details/NSIndexSet+ASHelpers.mm b/submodules/AsyncDisplayKit/Source/NSIndexSet+ASHelpers.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/Details/NSIndexSet+ASHelpers.mm rename to submodules/AsyncDisplayKit/Source/NSIndexSet+ASHelpers.mm index 0eba0358f4..615e1749f0 100644 --- a/submodules/AsyncDisplayKit/Source/Details/NSIndexSet+ASHelpers.mm +++ b/submodules/AsyncDisplayKit/Source/NSIndexSet+ASHelpers.mm @@ -10,7 +10,7 @@ // UIKit indexPath helpers #import -#import +#import "NSIndexSet+ASHelpers.h" @implementation NSIndexSet (ASHelpers) diff --git a/submodules/AsyncDisplayKit/Source/Private/ASBasicImageDownloaderInternal.h b/submodules/AsyncDisplayKit/Source/Private/ASBasicImageDownloaderInternal.h deleted file mode 100644 index ba06e0bc81..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASBasicImageDownloaderInternal.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// ASBasicImageDownloaderInternal.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -@interface ASBasicImageDownloaderContext : NSObject - -+ (ASBasicImageDownloaderContext *)contextForURL:(NSURL *)URL; - -@property (nonatomic, readonly) NSURL *URL; -@property (nonatomic, weak) NSURLSessionTask *sessionTask; - -- (BOOL)isCancelled; -- (void)cancel; - -@end diff --git a/submodules/AsyncDisplayKit/Source/Private/ASBatchFetching.h b/submodules/AsyncDisplayKit/Source/Private/ASBatchFetching.h deleted file mode 100644 index 0358bf12cc..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASBatchFetching.h +++ /dev/null @@ -1,77 +0,0 @@ -// -// ASBatchFetching.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -#import - -NS_ASSUME_NONNULL_BEGIN - -@class ASBatchContext; -@protocol ASBatchFetchingDelegate; - -@protocol ASBatchFetchingScrollView - -- (BOOL)canBatchFetch; -- (ASBatchContext *)batchContext; -- (CGFloat)leadingScreensForBatching; -- (nullable id)batchFetchingDelegate; - -@end - -/** - @abstract Determine if batch fetching should begin based on the state of the parameters. - @discussion This method is broken into a category for unit testing purposes and should be used with the ASTableView and - * ASCollectionView batch fetching API. - @param scrollView The scroll view that in-flight fetches are happening. - @param scrollDirection The current scrolling direction of the scroll view. - @param scrollableDirections The possible scrolling directions of the scroll view. - @param contentOffset The offset that the scrollview will scroll to. - @param velocity The velocity of the scroll view (in points) at the moment the touch was released. - @return Whether or not the current state should proceed with batch fetching. - */ -AS_EXTERN BOOL ASDisplayShouldFetchBatchForScrollView(UIScrollView *scrollView, - ASScrollDirection scrollDirection, - ASScrollDirection scrollableDirections, - CGPoint contentOffset, - CGPoint velocity); - - -/** - @abstract Determine if batch fetching should begin based on the state of the parameters. - @param context The batch fetching context that contains knowledge about in-flight fetches. - @param scrollDirection The current scrolling direction of the scroll view. - @param scrollableDirections The possible scrolling directions of the scroll view. - @param bounds The bounds of the scrollview. - @param contentSize The content size of the scrollview. - @param targetOffset The offset that the scrollview will scroll to. - @param leadingScreens How many screens in the remaining distance will trigger batch fetching. - @param visible Whether the view is visible or not. - @param velocity The velocity of the scroll view (in points) at the moment the touch was released. - @param delegate The delegate to be consulted if needed. - @return Whether or not the current state should proceed with batch fetching. - @discussion This method is broken into a category for unit testing purposes and should be used with the ASTableView and - * ASCollectionView batch fetching API. - */ -AS_EXTERN BOOL ASDisplayShouldFetchBatchForContext(ASBatchContext *context, - ASScrollDirection scrollDirection, - ASScrollDirection scrollableDirections, - CGRect bounds, - CGSize contentSize, - CGPoint targetOffset, - CGFloat leadingScreens, - BOOL visible, - CGPoint velocity, - _Nullable id delegate); - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Private/ASBatchFetching.mm b/submodules/AsyncDisplayKit/Source/Private/ASBatchFetching.mm deleted file mode 100644 index 44b31ed77d..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASBatchFetching.mm +++ /dev/null @@ -1,102 +0,0 @@ -// -// ASBatchFetching.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK -#import -#import -#import - -BOOL ASDisplayShouldFetchBatchForScrollView(UIScrollView *scrollView, - ASScrollDirection scrollDirection, - ASScrollDirection scrollableDirections, - CGPoint contentOffset, - CGPoint velocity) -{ - // Don't fetch if the scroll view does not allow - if (![scrollView canBatchFetch]) { - return NO; - } - - // Check if we should batch fetch - ASBatchContext *context = scrollView.batchContext; - CGRect bounds = scrollView.bounds; - CGSize contentSize = scrollView.contentSize; - CGFloat leadingScreens = scrollView.leadingScreensForBatching; - id delegate = scrollView.batchFetchingDelegate; - BOOL visible = (scrollView.window != nil); - return ASDisplayShouldFetchBatchForContext(context, scrollDirection, scrollableDirections, bounds, contentSize, contentOffset, leadingScreens, visible, velocity, delegate); -} - -BOOL ASDisplayShouldFetchBatchForContext(ASBatchContext *context, - ASScrollDirection scrollDirection, - ASScrollDirection scrollableDirections, - CGRect bounds, - CGSize contentSize, - CGPoint targetOffset, - CGFloat leadingScreens, - BOOL visible, - CGPoint velocity, - id delegate) -{ - // Do not allow fetching if a batch is already in-flight and hasn't been completed or cancelled - if ([context isFetching]) { - return NO; - } - - // No fetching for null states - if (leadingScreens <= 0.0 || CGRectIsEmpty(bounds)) { - return NO; - } - - - CGFloat viewLength, offset, contentLength, velocityLength; - if (ASScrollDirectionContainsVerticalDirection(scrollableDirections)) { - viewLength = bounds.size.height; - offset = targetOffset.y; - contentLength = contentSize.height; - velocityLength = velocity.y; - } else { // horizontal / right - viewLength = bounds.size.width; - offset = targetOffset.x; - contentLength = contentSize.width; - velocityLength = velocity.x; - } - - BOOL hasSmallContent = contentLength < viewLength; - if (hasSmallContent) { - return YES; - } - - // If we are not visible, but we do have enough content to fill visible area, - // don't batch fetch. - if (visible == NO) { - return NO; - } - - // If they are scrolling toward the head of content, don't batch fetch. - BOOL isScrollingTowardHead = (ASScrollDirectionContainsUp(scrollDirection) || ASScrollDirectionContainsLeft(scrollDirection)); - if (isScrollingTowardHead) { - return NO; - } - - CGFloat triggerDistance = viewLength * leadingScreens; - CGFloat remainingDistance = contentLength - viewLength - offset; - BOOL result = remainingDistance <= triggerDistance; - - if (delegate != nil && velocityLength > 0.0) { - // Don't need to get absolute value of remaining time - // because both remainingDistance and velocityLength are positive when scrolling toward tail - NSTimeInterval remainingTime = remainingDistance / (velocityLength * 1000); - result = [delegate shouldFetchBatchWithRemainingTime:remainingTime hint:result]; - } - - return result; -} - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Private/ASCellNode+Internal.h b/submodules/AsyncDisplayKit/Source/Private/ASCellNode+Internal.h deleted file mode 100644 index 4c3e63d652..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASCellNode+Internal.h +++ /dev/null @@ -1,79 +0,0 @@ -// -// ASCellNode+Internal.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK -#import - -NS_ASSUME_NONNULL_BEGIN - -@class ASCollectionElement; - -@protocol ASCellNodeInteractionDelegate - -/** - * Notifies the delegate that a specified cell node invalidates it's size what could result into a size change. - * - * @param node A node informing the delegate about the relayout. - */ -- (void)nodeDidInvalidateSize:(ASCellNode *)node; - -/* - * Methods to be called whenever the selection or highlight state changes - * on ASCellNode. UIKit internally stores these values to update reusable cells. - */ - -- (void)nodeSelectedStateDidChange:(ASCellNode *)node; -- (void)nodeHighlightedStateDidChange:(ASCellNode *)node; - -@end - -@interface ASCellNode () - -@property (nonatomic, weak) id interactionDelegate; - -/* - * Back-pointer to the containing scrollView instance, set only for visible cells. Used for Cell Visibility Event callbacks. - */ -@property (nonatomic, weak) UIScrollView *scrollView; - -- (void)__setSelectedFromUIKit:(BOOL)selected; -- (void)__setHighlightedFromUIKit:(BOOL)highlighted; - -/** - * @note This could be declared @c copy, but since this is only settable internally, we can ensure - * that it's always safe simply to retain it, and copy if needed. Since @c UICollectionViewLayoutAttributes - * is always mutable, @c copy is never "free" like it is for e.g. NSString. - */ -@property (nullable, nonatomic) UICollectionViewLayoutAttributes *layoutAttributes; - -@property (weak, nullable) ASCollectionElement *collectionElement; - -@property (weak, nullable) id owningNode; - -@property (nonatomic, readonly) BOOL shouldUseUIKitCell; - -@end - -@class ASWrapperCellNode; - -typedef CGSize (^ASSizeForItemBlock)(ASWrapperCellNode *node, CGSize collectionSize); -typedef UICollectionViewCell * _Nonnull(^ASCellForItemBlock)(ASWrapperCellNode *node); -typedef UICollectionReusableView * _Nonnull(^ASViewForSupplementaryBlock)(ASWrapperCellNode *node); - -@interface ASWrapperCellNode : ASCellNode - -@property (nonatomic, readonly) ASSizeForItemBlock sizeForItemBlock; -@property (nonatomic, readonly) ASCellForItemBlock cellForItemBlock; -@property (nonatomic, readonly) ASViewForSupplementaryBlock viewForSupplementaryBlock; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Private/ASCollectionLayout.h b/submodules/AsyncDisplayKit/Source/Private/ASCollectionLayout.h deleted file mode 100644 index 6ba15e96eb..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASCollectionLayout.h +++ /dev/null @@ -1,54 +0,0 @@ -// -// ASCollectionLayout.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import -#import - -@protocol ASCollectionLayoutDelegate; -@class ASElementMap, ASCollectionLayout, ASCollectionNode; - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED -@interface ASCollectionLayout : UICollectionViewLayout - -/** - * The collection node object currently using this layout object. - * - * @discussion The collection node object sets the value of this property when a new layout object is assigned to it. - * - * @discussion To get the truth on the current state of the collection, call methods on the collection node or the data source rather than the collection view because: - * 1. The view might not yet be allocated. - * 2. The collection node and data source are thread-safe. - */ -@property (nonatomic, weak) ASCollectionNode *collectionNode; - -@property (nonatomic, readonly) id layoutDelegate; - -/** - * Initializes with a layout delegate. - * - * @discussion For developers' convenience, the delegate is retained by this layout object, similar to UICollectionView retains its UICollectionViewLayout object. - * - * @discussion For simplicity, the delegate is read-only. If a new layout delegate is needed, construct a new layout object with that delegate and notify ASCollectionView about it. - * This ensures the underlying UICollectionView purges its cache and properly loads the new layout. - */ -- (instancetype)initWithLayoutDelegate:(id)layoutDelegate NS_DESIGNATED_INITIALIZER; - -- (instancetype)init __unavailable; - -- (instancetype)initWithCoder:(NSCoder *)aDecoder __unavailable; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Private/ASCollectionLayout.mm b/submodules/AsyncDisplayKit/Source/Private/ASCollectionLayout.mm deleted file mode 100644 index 59697f0820..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASCollectionLayout.mm +++ /dev/null @@ -1,390 +0,0 @@ -// -// ASCollectionLayout.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -static const ASRangeTuningParameters kASDefaultMeasureRangeTuningParameters = { - .leadingBufferScreenfuls = 2.0, - .trailingBufferScreenfuls = 2.0 -}; - -static const ASScrollDirection kASStaticScrollDirection = (ASScrollDirectionRight | ASScrollDirectionDown); - -@interface ASCollectionLayout () { - ASCollectionLayoutCache *_layoutCache; - ASCollectionLayoutState *_layout; // Main thread only. - - struct { - unsigned int implementsAdditionalInfoForLayoutWithElements:1; - } _layoutDelegateFlags; -} - -@end - -@implementation ASCollectionLayout - -- (instancetype)initWithLayoutDelegate:(id)layoutDelegate -{ - self = [super init]; - if (self) { - ASDisplayNodeAssertNotNil(layoutDelegate, @"Collection layout delegate cannot be nil"); - _layoutDelegate = layoutDelegate; - _layoutDelegateFlags.implementsAdditionalInfoForLayoutWithElements = [layoutDelegate respondsToSelector:@selector(additionalInfoForLayoutWithElements:)]; - _layoutCache = [[ASCollectionLayoutCache alloc] init]; - } - return self; -} - -#pragma mark - ASDataControllerLayoutDelegate - -- (ASCollectionLayoutContext *)layoutContextWithElements:(ASElementMap *)elements -{ - ASDisplayNodeAssertMainThread(); - - Class layoutDelegateClass = [_layoutDelegate class]; - ASCollectionLayoutCache *layoutCache = _layoutCache; - ASCollectionNode *collectionNode = _collectionNode; - if (collectionNode == nil) { - return [[ASCollectionLayoutContext alloc] initWithViewportSize:CGSizeZero - initialContentOffset:CGPointZero - scrollableDirections:ASScrollDirectionNone - elements:[[ASElementMap alloc] init] - layoutDelegateClass:layoutDelegateClass - layoutCache:layoutCache - additionalInfo:nil]; - } - - ASScrollDirection scrollableDirections = [_layoutDelegate scrollableDirections]; - CGSize viewportSize = [ASCollectionLayout _viewportSizeForCollectionNode:collectionNode scrollableDirections:scrollableDirections]; - CGPoint contentOffset = collectionNode.contentOffset; - - id additionalInfo = nil; - if (_layoutDelegateFlags.implementsAdditionalInfoForLayoutWithElements) { - additionalInfo = [_layoutDelegate additionalInfoForLayoutWithElements:elements]; - } - - return [[ASCollectionLayoutContext alloc] initWithViewportSize:viewportSize - initialContentOffset:contentOffset - scrollableDirections:scrollableDirections - elements:elements - layoutDelegateClass:layoutDelegateClass - layoutCache:layoutCache - additionalInfo:additionalInfo]; -} - -+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context -{ - if (context.elements == nil) { - return [[ASCollectionLayoutState alloc] initWithContext:context]; - } - - ASCollectionLayoutState *layout = [context.layoutDelegateClass calculateLayoutWithContext:context]; - [context.layoutCache setLayout:layout forContext:context]; - - // Measure elements in the measure range ahead of time - CGSize viewportSize = context.viewportSize; - CGPoint contentOffset = context.initialContentOffset; - CGRect initialRect = CGRectMake(contentOffset.x, contentOffset.y, viewportSize.width, viewportSize.height); - CGRect measureRect = CGRectExpandToRangeWithScrollableDirections(initialRect, - kASDefaultMeasureRangeTuningParameters, - context.scrollableDirections, - kASStaticScrollDirection); - // The first call to -layoutAttributesForElementsInRect: will be with a rect that is way bigger than initialRect here. - // If we only block on initialRect, a few elements that are outside of initialRect but inside measureRect - // may not be available by the time -layoutAttributesForElementsInRect: is called. - // Since this method is usually run off main, let's spawn more threads to measure and block on all elements in measureRect. - [self _measureElementsInRect:measureRect blockingRect:measureRect layout:layout]; - - return layout; -} - -#pragma mark - UICollectionViewLayout overrides - -- (void)prepareLayout -{ - ASDisplayNodeAssertMainThread(); - [super prepareLayout]; - - ASCollectionLayoutContext *context = [self layoutContextWithElements:_collectionNode.visibleElements]; - if (_layout != nil && ASObjectIsEqual(_layout.context, context)) { - // The existing layout is still valid. No-op - return; - } - - if (ASCollectionLayoutState *cachedLayout = [_layoutCache layoutForContext:context]) { - _layout = cachedLayout; - } else { - // A new layout is needed now. Calculate and apply it immediately - _layout = [ASCollectionLayout calculateLayoutWithContext:context]; - } -} - -- (void)invalidateLayout -{ - ASDisplayNodeAssertMainThread(); - [super invalidateLayout]; - if (_layout != nil) { - [_layoutCache removeLayoutForContext:_layout.context]; - _layout = nil; - } -} - -/** - * NOTE: It is suggested practice on the Web to override invalidationContextForInteractivelyMovingItems… and call out to the - * data source to move the item (so that if e.g. the item size depends on the data, you get the data you expect). However, as of iOS 11 this - * doesn't work, because UICV machinery will also call out to the data source to move the item after the interaction is done. The result is - * that your data source state will be incorrect due to this last move call. Plus it's just an API violation. - * - * Things tried: - * - Doing the speculative data source moves, and then UNDOING the last one in invalidationContextForEndingInteractiveMovementOfItems… - * but this does not work because the UICV machinery informs its data source before it calls that method on us, so we are too late. - * - * The correct practice is to use the UIDataSourceTranslating API introduced in iOS 11. Currently Texture does not support this API but we can - * build it if there is demand. We could add an id field onto the layout context object, and the layout client can - * use data source index paths when it reads nodes or other data source data. - */ - -- (CGSize)collectionViewContentSize -{ - ASDisplayNodeAssertMainThread(); - // The content size can be queried right after a layout invalidation (https://github.com/TextureGroup/Texture/pull/509). - // In that case, return zero. - return _layout ? _layout.contentSize : CGSizeZero; -} - -- (NSArray *)layoutAttributesForElementsInRect:(CGRect)blockingRect -{ - ASDisplayNodeAssertMainThread(); - if (CGRectIsEmpty(blockingRect)) { - return nil; - } - - // Measure elements in the measure range, block on the requested rect - CGRect measureRect = CGRectExpandToRangeWithScrollableDirections(blockingRect, - kASDefaultMeasureRangeTuningParameters, - _layout.context.scrollableDirections, - kASStaticScrollDirection); - [ASCollectionLayout _measureElementsInRect:measureRect blockingRect:blockingRect layout:_layout]; - - NSArray *result = [_layout layoutAttributesForElementsInRect:blockingRect]; - - ASElementMap *elements = _layout.context.elements; - for (UICollectionViewLayoutAttributes *attrs in result) { - ASCollectionElement *element = [elements elementForLayoutAttributes:attrs]; - ASCollectionLayoutSetSizeToElement(attrs.frame.size, element); - } - - return result; -} - -- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertMainThread(); - - ASCollectionElement *element = [_layout.context.elements elementForItemAtIndexPath:indexPath]; - UICollectionViewLayoutAttributes *attrs = [_layout layoutAttributesForElement:element]; - - ASCellNode *node = element.node; - CGSize elementSize = attrs.frame.size; - if (! CGSizeEqualToSize(elementSize, node.calculatedSize)) { - [node layoutThatFits:ASCollectionLayoutElementSizeRangeFromSize(elementSize)]; - } - - ASCollectionLayoutSetSizeToElement(attrs.frame.size, element); - return attrs; -} - -- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath -{ - ASCollectionElement *element = [_layout.context.elements supplementaryElementOfKind:elementKind atIndexPath:indexPath]; - UICollectionViewLayoutAttributes *attrs = [_layout layoutAttributesForElement:element]; - - ASCellNode *node = element.node; - CGSize elementSize = attrs.frame.size; - if (! CGSizeEqualToSize(elementSize, node.calculatedSize)) { - [node layoutThatFits:ASCollectionLayoutElementSizeRangeFromSize(elementSize)]; - } - - ASCollectionLayoutSetSizeToElement(attrs.frame.size, element); - return attrs; -} - -- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds -{ - return (! CGSizeEqualToSize([ASCollectionLayout _boundsForCollectionNode:_collectionNode], newBounds.size)); -} - -#pragma mark - Private methods - -+ (CGSize)_boundsForCollectionNode:(nonnull ASCollectionNode *)collectionNode -{ - if (collectionNode == nil) { - return CGSizeZero; - } - - if (!collectionNode.isNodeLoaded) { - // TODO consider calculatedSize as well - return collectionNode.threadSafeBounds.size; - } - - ASDisplayNodeAssertMainThread(); - return collectionNode.view.bounds.size; -} - -+ (CGSize)_viewportSizeForCollectionNode:(nonnull ASCollectionNode *)collectionNode scrollableDirections:(ASScrollDirection)scrollableDirections -{ - if (collectionNode == nil) { - return CGSizeZero; - } - - CGSize result = [ASCollectionLayout _boundsForCollectionNode:collectionNode]; - // TODO: Consider using adjustedContentInset on iOS 11 and later, to include the safe area of the scroll view - UIEdgeInsets contentInset = collectionNode.contentInset; - if (ASScrollDirectionContainsHorizontalDirection(scrollableDirections)) { - result.height -= (contentInset.top + contentInset.bottom); - } else { - result.width -= (contentInset.left + contentInset.right); - } - return result; -} - -/** - * Measures all elements in the specified rect and blocks the calling thread while measuring those in the blocking rect. - */ -+ (void)_measureElementsInRect:(CGRect)rect blockingRect:(CGRect)blockingRect layout:(ASCollectionLayoutState *)layout -{ - if (CGRectIsEmpty(rect) || layout.context.elements == nil) { - return; - } - BOOL hasBlockingRect = !CGRectIsEmpty(blockingRect); - if (hasBlockingRect && CGRectContainsRect(rect, blockingRect) == NO) { - ASDisplayNodeCAssert(NO, @"Blocking rect, if specified, must be within the other (outer) rect"); - return; - } - - // Step 1: Clamp the specified rects between the bounds of content rect - CGSize contentSize = layout.contentSize; - CGRect contentRect = CGRectMake(0, 0, contentSize.width, contentSize.height); - rect = CGRectIntersection(contentRect, rect); - if (CGRectIsNull(rect)) { - return; - } - if (hasBlockingRect) { - blockingRect = CGRectIntersection(contentRect, blockingRect); - hasBlockingRect = !CGRectIsNull(blockingRect); - } - - // Step 2: Get layout attributes of all elements within the specified outer rect - ASPageToLayoutAttributesTable *attrsTable = [layout getAndRemoveUnmeasuredLayoutAttributesPageTableInRect:rect]; - if (attrsTable.count == 0) { - // No elements in this rect! Bail early - return; - } - - // Step 3: Split all those attributes into blocking and non-blocking buckets - // Use ordered sets here because some items may span multiple pages, and the sets will be accessed by indexes later on. - ASCollectionLayoutContext *context = layout.context; - CGSize pageSize = context.viewportSize; - NSMutableOrderedSet *blockingAttrs = hasBlockingRect ? [NSMutableOrderedSet orderedSet] : nil; - NSMutableOrderedSet *nonBlockingAttrs = [NSMutableOrderedSet orderedSet]; - for (id pagePtr in attrsTable) { - ASPageCoordinate page = (ASPageCoordinate)pagePtr; - NSArray *attrsInPage = [attrsTable objectForPage:page]; - // Calculate the page's rect but only if it's going to be used. - CGRect pageRect = hasBlockingRect ? ASPageCoordinateGetPageRect(page, pageSize) : CGRectZero; - - if (hasBlockingRect && CGRectContainsRect(blockingRect, pageRect)) { - // The page fits well within the blocking rect. All attributes in this page are blocking. - [blockingAttrs addObjectsFromArray:attrsInPage]; - } else if (hasBlockingRect && CGRectIntersectsRect(blockingRect, pageRect)) { - // The page intersects the blocking rect. Some elements in this page are blocking, some are not. - for (UICollectionViewLayoutAttributes *attrs in attrsInPage) { - if (CGRectIntersectsRect(blockingRect, attrs.frame)) { - [blockingAttrs addObject:attrs]; - } else { - [nonBlockingAttrs addObject:attrs]; - } - } - } else { - // The page doesn't intersect the blocking rect. All elements in this page are non-blocking. - [nonBlockingAttrs addObjectsFromArray:attrsInPage]; - } - } - - // Step 4: Allocate and measure blocking elements' node - ASElementMap *elements = context.elements; - dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - if (NSUInteger count = blockingAttrs.count) { - ASDispatchApply(count, queue, 0, ^(size_t i) { - UICollectionViewLayoutAttributes *attrs = blockingAttrs[i]; - ASCellNode *node = [elements elementForItemAtIndexPath:attrs.indexPath].node; - CGSize expectedSize = attrs.frame.size; - if (! CGSizeEqualToSize(expectedSize, node.calculatedSize)) { - [node layoutThatFits:ASCollectionLayoutElementSizeRangeFromSize(expectedSize)]; - } - }); - } - - // Step 5: Allocate and measure non-blocking ones - if (NSUInteger count = nonBlockingAttrs.count) { - __weak ASElementMap *weakElements = elements; - ASDispatchAsync(count, queue, 0, ^(size_t i) { - __strong ASElementMap *strongElements = weakElements; - if (strongElements) { - UICollectionViewLayoutAttributes *attrs = nonBlockingAttrs[i]; - ASCellNode *node = [elements elementForItemAtIndexPath:attrs.indexPath].node; - CGSize expectedSize = attrs.frame.size; - if (! CGSizeEqualToSize(expectedSize, node.calculatedSize)) { - [node layoutThatFits:ASCollectionLayoutElementSizeRangeFromSize(expectedSize)]; - } - } - }); - } -} - -# pragma mark - Convenient inline functions - -ASDISPLAYNODE_INLINE ASSizeRange ASCollectionLayoutElementSizeRangeFromSize(CGSize size) -{ - // The layout delegate consulted us that this element must fit within this size, - // and the only way to achieve that without asking it again is to use an exact size range here. - return ASSizeRangeMake(size); -} - -ASDISPLAYNODE_INLINE void ASCollectionLayoutSetSizeToElement(CGSize size, ASCollectionElement *element) -{ - if (ASCellNode *node = element.node) { - if (! CGSizeEqualToSize(size, node.frame.size)) { - CGRect frame = CGRectZero; - frame.size = size; - node.frame = frame; - } - } -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Private/ASCollectionLayoutCache.h b/submodules/AsyncDisplayKit/Source/Private/ASCollectionLayoutCache.h deleted file mode 100644 index 857e43683f..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASCollectionLayoutCache.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// ASCollectionLayoutCache.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@class ASCollectionLayoutContext, ASCollectionLayoutState; - -/// A thread-safe cache for ASCollectionLayoutContext-ASCollectionLayoutState pairs -AS_SUBCLASSING_RESTRICTED -@interface ASCollectionLayoutCache : NSObject - -- (nullable ASCollectionLayoutState *)layoutForContext:(ASCollectionLayoutContext *)context; - -- (void)setLayout:(ASCollectionLayoutState *)layout forContext:(ASCollectionLayoutContext *)context; - -- (void)removeLayoutForContext:(ASCollectionLayoutContext *)context; - -- (void)removeAllLayouts; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Private/ASCollectionLayoutCache.mm b/submodules/AsyncDisplayKit/Source/Private/ASCollectionLayoutCache.mm deleted file mode 100644 index b5aecd78b6..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASCollectionLayoutCache.mm +++ /dev/null @@ -1,92 +0,0 @@ -// -// ASCollectionLayoutCache.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -#import -#import -#import -#import - -using AS::MutexLocker; - -@implementation ASCollectionLayoutCache { - AS::Mutex __instanceLock__; - - /** - * The underlying data structure of this cache. - * - * The outer map table is a weak to strong table. That is because ASCollectionLayoutContext doesn't (and shouldn't) - * hold a strong reference on its element map. As a result, this cache should handle the case in which - * an element map no longer exists and all contexts and layouts associated with it should be cleared. - * - * The inner map table is a standard strong to strong map. - * Since different ASCollectionLayoutContext objects with the same content are considered equal, - * "object pointer personality" can't be used as a key option. - */ - NSMapTable *> *_map; -} - -- (instancetype)init -{ - self = [super init]; - if (self) { - _map = [NSMapTable mapTableWithKeyOptions:(NSMapTableWeakMemory | NSMapTableObjectPointerPersonality) valueOptions:NSMapTableStrongMemory]; - } - return self; -} - -- (ASCollectionLayoutState *)layoutForContext:(ASCollectionLayoutContext *)context -{ - ASElementMap *elements = context.elements; - if (elements == nil) { - return nil; - } - - MutexLocker l(__instanceLock__); - return [[_map objectForKey:elements] objectForKey:context]; -} - -- (void)setLayout:(ASCollectionLayoutState *)layout forContext:(ASCollectionLayoutContext *)context -{ - ASElementMap *elements = context.elements; - if (layout == nil || elements == nil) { - return; - } - - MutexLocker l(__instanceLock__); - auto innerMap = [_map objectForKey:elements]; - if (innerMap == nil) { - innerMap = [NSMapTable strongToStrongObjectsMapTable]; - [_map setObject:innerMap forKey:elements]; - } - [innerMap setObject:layout forKey:context]; -} - -- (void)removeLayoutForContext:(ASCollectionLayoutContext *)context -{ - ASElementMap *elements = context.elements; - if (elements == nil) { - return; - } - - MutexLocker l(__instanceLock__); - [[_map objectForKey:elements] removeObjectForKey:context]; -} - -- (void)removeAllLayouts -{ - MutexLocker l(__instanceLock__); - [_map removeAllObjects]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Private/ASCollectionLayoutContext+Private.h b/submodules/AsyncDisplayKit/Source/Private/ASCollectionLayoutContext+Private.h deleted file mode 100644 index 2ff68e17bc..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASCollectionLayoutContext+Private.h +++ /dev/null @@ -1,36 +0,0 @@ -// -// ASCollectionLayoutContext+Private.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -@class ASCollectionLayoutCache; -@protocol ASCollectionLayoutDelegate; - -NS_ASSUME_NONNULL_BEGIN - -@interface ASCollectionLayoutContext (Private) - -@property (nonatomic, readonly) Class layoutDelegateClass; -@property (nonatomic, weak, readonly) ASCollectionLayoutCache *layoutCache; - -- (instancetype)initWithViewportSize:(CGSize)viewportSize - initialContentOffset:(CGPoint)initialContentOffset - scrollableDirections:(ASScrollDirection)scrollableDirections - elements:(ASElementMap *)elements - layoutDelegateClass:(Class)layoutDelegateClass - layoutCache:(ASCollectionLayoutCache *)layoutCache - additionalInfo:(nullable id)additionalInfo; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Private/ASCollectionLayoutDefines.h b/submodules/AsyncDisplayKit/Source/Private/ASCollectionLayoutDefines.h deleted file mode 100644 index 61a8ac4e88..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASCollectionLayoutDefines.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// ASCollectionLayoutDefines.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -AS_EXTERN ASSizeRange ASSizeRangeForCollectionLayoutThatFitsViewportSize(CGSize viewportSize, ASScrollDirection scrollableDirections) AS_WARN_UNUSED_RESULT; - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Private/ASCollectionLayoutDefines.mm b/submodules/AsyncDisplayKit/Source/Private/ASCollectionLayoutDefines.mm deleted file mode 100644 index e386575c5d..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASCollectionLayoutDefines.mm +++ /dev/null @@ -1,23 +0,0 @@ -// -// ASCollectionLayoutDefines.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -ASSizeRange ASSizeRangeForCollectionLayoutThatFitsViewportSize(CGSize viewportSize, ASScrollDirection scrollableDirections) -{ - ASSizeRange sizeRange = ASSizeRangeUnconstrained; - if (ASScrollDirectionContainsVerticalDirection(scrollableDirections) == NO) { - sizeRange.min.height = viewportSize.height; - sizeRange.max.height = viewportSize.height; - } - if (ASScrollDirectionContainsHorizontalDirection(scrollableDirections) == NO) { - sizeRange.min.width = viewportSize.width; - sizeRange.max.width = viewportSize.width; - } - return sizeRange; -} diff --git a/submodules/AsyncDisplayKit/Source/Private/ASCollectionLayoutState+Private.h b/submodules/AsyncDisplayKit/Source/Private/ASCollectionLayoutState+Private.h deleted file mode 100644 index c4d6013332..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASCollectionLayoutState+Private.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// ASCollectionLayoutState+Private.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface ASCollectionLayoutState (Private) - -/** - * Remove and returns layout attributes for unmeasured elements that intersect the specified rect - * - * @discussion This method is atomic and thread-safe - */ -- (nullable ASPageToLayoutAttributesTable *)getAndRemoveUnmeasuredLayoutAttributesPageTableInRect:(CGRect)rect; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Private/ASCollectionView+Undeprecated.h b/submodules/AsyncDisplayKit/Source/Private/ASCollectionView+Undeprecated.h deleted file mode 100644 index 050e029313..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASCollectionView+Undeprecated.h +++ /dev/null @@ -1,303 +0,0 @@ -// -// ASCollectionView+Undeprecated.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * Currently our public collection API is on @c ASCollectionNode and the @c ASCollectionView - * API is deprecated, but the implementations still live in the view. - * - * This category lets us avoid deprecation warnings everywhere internally. - * In the future, the @c ASCollectionView public API will be eliminated and so will this file. - */ -@interface ASCollectionView (Undeprecated) - -/** - * The object that acts as the asynchronous delegate of the collection view - * - * @discussion The delegate must adopt the ASCollectionDelegate protocol. The collection view maintains a weak reference to the delegate object. - * - * The delegate object is responsible for providing size constraints for nodes and indicating whether batch fetching should begin. - */ -@property (nonatomic, weak) id asyncDelegate; - -/** - * The object that acts as the asynchronous data source of the collection view - * - * @discussion The datasource must adopt the ASCollectionDataSource protocol. The collection view maintains a weak reference to the datasource object. - * - * The datasource object is responsible for providing nodes or node creation blocks to the collection view. - */ -@property (nonatomic, weak) id asyncDataSource; - -/** - * Initializes an ASCollectionView - * - * @discussion Initializes and returns a newly allocated collection view object with the specified layout. - * - * @param layout The layout object to use for organizing items. The collection view stores a strong reference to the specified object. Must not be nil. - */ -- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout; - -/** - * Initializes an ASCollectionView - * - * @discussion Initializes and returns a newly allocated collection view object with the specified frame and layout. - * - * @param frame The frame rectangle for the collection view, measured in points. The origin of the frame is relative to the superview in which you plan to add it. This frame is passed to the superclass during initialization. - * @param layout The layout object to use for organizing items. The collection view stores a strong reference to the specified object. Must not be nil. - */ -- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout; - -@property (nonatomic) CGFloat leadingScreensForBatching; - -@property (nonatomic) BOOL inverted; - -@property (nonatomic, readonly) ASScrollDirection scrollDirection; - -@property (nonatomic, readonly) ASScrollDirection scrollableDirections; - -@property (nonatomic, weak) id layoutInspector; - -@property (nonatomic) UIEdgeInsets contentInset; - -@property (nonatomic) CGPoint contentOffset; - -/** - * Tuning parameters for a range type in full mode. - * - * @param rangeType The range type to get the tuning parameters for. - * - * @return A tuning parameter value for the given range type in full mode. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType AS_WARN_UNUSED_RESULT; - -/** - * Set the tuning parameters for a range type in full mode. - * - * @param tuningParameters The tuning parameters to store for a range type. - * @param rangeType The range type to set the tuning parameters for. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType; - -/** - * Tuning parameters for a range type in the specified mode. - * - * @param rangeMode The range mode to get the running parameters for. - * @param rangeType The range type to get the tuning parameters for. - * - * @return A tuning parameter value for the given range type in the given mode. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType AS_WARN_UNUSED_RESULT; - -/** - * Set the tuning parameters for a range type in the specified mode. - * - * @param tuningParameters The tuning parameters to store for a range type. - * @param rangeMode The range mode to set the running parameters for. - * @param rangeType The range type to set the tuning parameters for. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; - -- (nullable __kindof UICollectionViewCell *)cellForItemAtIndexPath:(NSIndexPath *)indexPath; - -@property (nonatomic, readonly) NSArray *indexPathsForVisibleItems; - -@property (nonatomic, readonly, nullable) NSArray *indexPathsForSelectedItems; - -/** - * Scrolls the collection to the given item. - * - * @param indexPath The index path of the item. - * @param scrollPosition Where the row should end up after the scroll. - * @param animated Whether the scroll should be animated or not. - */ -- (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated; - -- (void)selectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UICollectionViewScrollPosition)scrollPosition; - -/** - * Perform a batch of updates asynchronously, optionally disabling all animations in the batch. This method must be called from the main thread. - * The asyncDataSource must be updated to reflect the changes before the update block completes. - * - * @param animated NO to disable animations for this batch - * @param updates The block that performs the relevant insert, delete, reload, or move operations. - * @param completion A completion handler block to execute when all of the operations are finished. This block takes a single - * Boolean parameter that contains the value YES if all of the related animations completed successfully or - * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. - */ -- (void)performBatchAnimated:(BOOL)animated updates:(nullable AS_NOESCAPE void (^)(void))updates completion:(nullable void (^)(BOOL finished))completion; - -/** - * Perform a batch of updates asynchronously. This method must be called from the main thread. - * The asyncDataSource must be updated to reflect the changes before update block completes. - * - * @param updates The block that performs the relevant insert, delete, reload, or move operations. - * @param completion A completion handler block to execute when all of the operations are finished. This block takes a single - * Boolean parameter that contains the value YES if all of the related animations completed successfully or - * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. - */ -- (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)(void))updates completion:(nullable void (^)(BOOL finished))completion; - -/** - * Triggers a relayout of all nodes. - * - * @discussion This method invalidates and lays out every cell node in the collection. - */ -- (void)relayoutItems; - -/** - * Blocks execution of the main thread until all section and row updates are committed. This method must be called from the main thread. - */ -- (void)waitUntilAllUpdatesAreCommitted; - -/** - * Registers the given kind of supplementary node for use in creating node-backed supplementary views. - * - * @param elementKind The kind of supplementary node that will be requested through the data source. - * - * @discussion Use this method to register support for the use of supplementary nodes in place of the default - * `registerClass:forSupplementaryViewOfKind:withReuseIdentifier:` and `registerNib:forSupplementaryViewOfKind:withReuseIdentifier:` - * methods. This method will register an internal backing view that will host the contents of the supplementary nodes - * returned from the data source. - */ -- (void)registerSupplementaryNodeOfKind:(NSString *)elementKind; - -/** - * Inserts one or more sections. - * - * @param sections An index set that specifies the sections to insert. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)insertSections:(NSIndexSet *)sections; - -/** - * Deletes one or more sections. - * - * @param sections An index set that specifies the sections to delete. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)deleteSections:(NSIndexSet *)sections; - -/** - * Reloads the specified sections. - * - * @param sections An index set that specifies the sections to reload. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)reloadSections:(NSIndexSet *)sections; - -/** - * Moves a section to a new location. - * - * @param section The index of the section to move. - * - * @param newSection The index that is the destination of the move for the section. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection; - -/** - * Inserts items at the locations identified by an array of index paths. - * - * @param indexPaths An array of NSIndexPath objects, each representing an item index and section index that together identify an item. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)insertItemsAtIndexPaths:(NSArray *)indexPaths; - -/** - * Deletes the items specified by an array of index paths. - * - * @param indexPaths An array of NSIndexPath objects identifying the items to delete. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths; - -/** - * Reloads the specified items. - * - * @param indexPaths An array of NSIndexPath objects identifying the items to reload. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths; - -/** - * Moves the item at a specified location to a destination location. - * - * @param indexPath The index path identifying the item to move. - * - * @param newIndexPath The index path that is the destination of the move for the item. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath; - -/** - * Similar to -visibleCells. - * - * @return an array containing the nodes being displayed on screen. - */ -- (NSArray<__kindof ASCellNode *> *)visibleNodes AS_WARN_UNUSED_RESULT; - -/** - * Similar to -indexPathForCell:. - * - * @param cellNode a cellNode in the collection view - * - * @return The index path for this cell node. - * - * @discussion This index path returned by this method is in the _view's_ index space - * and should only be used with @c ASCollectionView directly. To get an index path suitable - * for use with your data source and @c ASCollectionNode, call @c indexPathForNode: on the - * collection node instead. - */ -- (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode AS_WARN_UNUSED_RESULT; - -/** - * Invalidates and recalculates the cached sizes stored for pass-through cells used in interop mode. - */ -- (void)invalidateFlowLayoutDelegateMetrics; - -- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Private/ASCollectionViewFlowLayoutInspector.h b/submodules/AsyncDisplayKit/Source/Private/ASCollectionViewFlowLayoutInspector.h deleted file mode 100644 index 0553dcd0b6..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASCollectionViewFlowLayoutInspector.h +++ /dev/null @@ -1,33 +0,0 @@ -// -// ASCollectionViewFlowLayoutInspector.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK -#import - -NS_ASSUME_NONNULL_BEGIN - -@class ASCollectionView; -@class UICollectionViewFlowLayout; - -/** - * A layout inspector implementation specific for the sizing behavior of UICollectionViewFlowLayouts - */ -AS_SUBCLASSING_RESTRICTED -@interface ASCollectionViewFlowLayoutInspector : NSObject - -@property (nonatomic, weak, readonly) UICollectionViewFlowLayout *layout; - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithFlowLayout:(UICollectionViewFlowLayout *)flowLayout NS_DESIGNATED_INITIALIZER; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Private/ASCollectionViewFlowLayoutInspector.mm b/submodules/AsyncDisplayKit/Source/Private/ASCollectionViewFlowLayoutInspector.mm deleted file mode 100644 index 6dc9d73bfb..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASCollectionViewFlowLayoutInspector.mm +++ /dev/null @@ -1,159 +0,0 @@ -// -// ASCollectionViewFlowLayoutInspector.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// -#ifndef MINIMAL_ASDK -#import -#import -#import -#import -#import -#import - -#define kDefaultItemSize CGSizeMake(50, 50) - -#pragma mark - ASCollectionViewFlowLayoutInspector - -@interface ASCollectionViewFlowLayoutInspector () -@property (nonatomic, weak) UICollectionViewFlowLayout *layout; -@end - -@implementation ASCollectionViewFlowLayoutInspector { - struct { - unsigned int implementsSizeRangeForHeader:1; - unsigned int implementsReferenceSizeForHeader:1; - unsigned int implementsSizeRangeForFooter:1; - unsigned int implementsReferenceSizeForFooter:1; - unsigned int implementsConstrainedSizeForNodeAtIndexPathDeprecated:1; - unsigned int implementsConstrainedSizeForItemAtIndexPath:1; - } _delegateFlags; -} - -#pragma mark Lifecycle - -- (instancetype)initWithFlowLayout:(UICollectionViewFlowLayout *)flowLayout; -{ - NSParameterAssert(flowLayout); - - self = [super init]; - if (self != nil) { - _layout = flowLayout; - } - return self; -} - -#pragma mark ASCollectionViewLayoutInspecting - -- (void)didChangeCollectionViewDelegate:(id)delegate; -{ - if (delegate == nil) { - memset(&_delegateFlags, 0, sizeof(_delegateFlags)); - } else { - _delegateFlags.implementsSizeRangeForHeader = [delegate respondsToSelector:@selector(collectionNode:sizeRangeForHeaderInSection:)]; - _delegateFlags.implementsReferenceSizeForHeader = [delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)]; - _delegateFlags.implementsSizeRangeForFooter = [delegate respondsToSelector:@selector(collectionNode:sizeRangeForFooterInSection:)]; - _delegateFlags.implementsReferenceSizeForFooter = [delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForFooterInSection:)]; - _delegateFlags.implementsConstrainedSizeForNodeAtIndexPathDeprecated = [delegate respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)]; - _delegateFlags.implementsConstrainedSizeForItemAtIndexPath = [delegate respondsToSelector:@selector(collectionNode:constrainedSizeForItemAtIndexPath:)]; - } -} - -- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath -{ - ASSizeRange result = ASSizeRangeUnconstrained; - if (_delegateFlags.implementsConstrainedSizeForItemAtIndexPath) { - result = [collectionView.asyncDelegate collectionNode:collectionView.collectionNode constrainedSizeForItemAtIndexPath:indexPath]; - } else if (_delegateFlags.implementsConstrainedSizeForNodeAtIndexPathDeprecated) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - result = [collectionView.asyncDelegate collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } else { - // With 2.0 `collectionView:constrainedSizeForNodeAtIndexPath:` was moved to the delegate. Assert if not implemented on the delegate but on the data source - ASDisplayNodeAssert([collectionView.asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)] == NO, @"collectionView:constrainedSizeForNodeAtIndexPath: was moved from the ASCollectionDataSource to the ASCollectionDelegate."); - } - - // If we got no size range: - if (ASSizeRangeEqualToSizeRange(result, ASSizeRangeUnconstrained)) { - // Use itemSize if they set it. - CGSize itemSize = _layout.itemSize; - if (CGSizeEqualToSize(itemSize, kDefaultItemSize) == NO) { - result = ASSizeRangeMake(itemSize, itemSize); - } else { - // Compute constraint from scroll direction otherwise. - result = NodeConstrainedSizeForScrollDirection(collectionView); - } - } - - return result; -} - -- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - ASSizeRange result = ASSizeRangeZero; - if (ASObjectIsEqual(kind, UICollectionElementKindSectionHeader)) { - if (_delegateFlags.implementsSizeRangeForHeader) { - result = [[self delegateForCollectionView:collectionView] collectionNode:collectionView.collectionNode sizeRangeForHeaderInSection:indexPath.section]; - } else if (_delegateFlags.implementsReferenceSizeForHeader) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - CGSize exactSize = [[self delegateForCollectionView:collectionView] collectionView:collectionView layout:_layout referenceSizeForHeaderInSection:indexPath.section]; -#pragma clang diagnostic pop - result = ASSizeRangeMake(exactSize); - } else { - result = ASSizeRangeMake(_layout.headerReferenceSize); - } - } else if (ASObjectIsEqual(kind, UICollectionElementKindSectionFooter)) { - if (_delegateFlags.implementsSizeRangeForFooter) { - result = [[self delegateForCollectionView:collectionView] collectionNode:collectionView.collectionNode sizeRangeForFooterInSection:indexPath.section]; - } else if (_delegateFlags.implementsReferenceSizeForFooter) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - CGSize exactSize = [[self delegateForCollectionView:collectionView] collectionView:collectionView layout:_layout referenceSizeForFooterInSection:indexPath.section]; -#pragma clang diagnostic pop - result = ASSizeRangeMake(exactSize); - } else { - result = ASSizeRangeMake(_layout.footerReferenceSize); - } - } else { - ASDisplayNodeFailAssert(@"Unexpected supplementary kind: %@", kind); - return ASSizeRangeZero; - } - - if (_layout.scrollDirection == UICollectionViewScrollDirectionVertical) { - result.min.width = result.max.width = CGRectGetWidth(collectionView.bounds); - } else { - result.min.height = result.max.height = CGRectGetHeight(collectionView.bounds); - } - return result; -} - -- (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section -{ - ASSizeRange constraint = [self collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]]; - if (_layout.scrollDirection == UICollectionViewScrollDirectionVertical) { - return (constraint.max.height > 0 ? 1 : 0); - } else { - return (constraint.max.width > 0 ? 1 : 0); - } -} - -- (ASScrollDirection)scrollableDirections -{ - return (self.layout.scrollDirection == UICollectionViewScrollDirectionHorizontal) ? ASScrollDirectionHorizontalDirections : ASScrollDirectionVerticalDirections; -} - -#pragma mark - Private helpers - -- (id)delegateForCollectionView:(ASCollectionView *)collectionView -{ - return (id)collectionView.asyncDelegate; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Private/ASDefaultPlayButton.h b/submodules/AsyncDisplayKit/Source/Private/ASDefaultPlayButton.h deleted file mode 100644 index e3bc15246d..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASDefaultPlayButton.h +++ /dev/null @@ -1,14 +0,0 @@ -// -// ASDefaultPlayButton.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@interface ASDefaultPlayButton : ASButtonNode - -@end diff --git a/submodules/AsyncDisplayKit/Source/Private/ASDefaultPlayButton.mm b/submodules/AsyncDisplayKit/Source/Private/ASDefaultPlayButton.mm deleted file mode 100644 index 589bda90c2..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASDefaultPlayButton.mm +++ /dev/null @@ -1,66 +0,0 @@ -// -// ASDefaultPlayButton.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -@implementation ASDefaultPlayButton - -- (instancetype)init -{ - if (!(self = [super init])) { - return nil; - } - - self.opaque = NO; - - return self; -} - -+ (void)drawRect:(CGRect)bounds withParameters:(id)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing -{ - CGFloat originX = bounds.size.width/4; - CGRect buttonBounds = CGRectMake(originX, bounds.size.height/4, bounds.size.width/2, bounds.size.height/2); - CGFloat widthHeight = buttonBounds.size.width; - - //When the video isn't a square, the lower bound should be used to figure out the circle size - if (bounds.size.width < bounds.size.height) { - widthHeight = bounds.size.width/2; - originX = (bounds.size.width - widthHeight)/2; - buttonBounds = CGRectMake(originX, (bounds.size.height - widthHeight)/2, widthHeight, widthHeight); - } - if (bounds.size.width > bounds.size.height) { - widthHeight = bounds.size.height/2; - originX = (bounds.size.width - widthHeight)/2; - buttonBounds = CGRectMake(originX, (bounds.size.height - widthHeight)/2, widthHeight, widthHeight); - } - - CGContextRef context = UIGraphicsGetCurrentContext(); - - // Circle Drawing - UIBezierPath *ovalPath = [UIBezierPath bezierPathWithOvalInRect: buttonBounds]; - [[UIColor colorWithWhite:0.0 alpha:0.5] setFill]; - [ovalPath fill]; - - // Triangle Drawing - CGContextSaveGState(context); - - UIBezierPath *trianglePath = [UIBezierPath bezierPath]; - [trianglePath moveToPoint:CGPointMake(originX + widthHeight/3, bounds.size.height/4 + (bounds.size.height/2)/4)]; - [trianglePath addLineToPoint:CGPointMake(originX + widthHeight/3, bounds.size.height - bounds.size.height/4 - (bounds.size.height/2)/4)]; - [trianglePath addLineToPoint:CGPointMake(bounds.size.width - originX - widthHeight/4, bounds.size.height/2)]; - - [trianglePath closePath]; - [[UIColor colorWithWhite:0.9 alpha:0.9] setFill]; - [trianglePath fill]; - - CGContextRestoreGState(context); -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/Private/ASDefaultPlaybackButton.h b/submodules/AsyncDisplayKit/Source/Private/ASDefaultPlaybackButton.h deleted file mode 100644 index ae7e245dc0..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASDefaultPlaybackButton.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// ASDefaultPlaybackButton.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -typedef NS_ENUM(NSInteger, ASDefaultPlaybackButtonType) { - ASDefaultPlaybackButtonTypePlay, - ASDefaultPlaybackButtonTypePause -}; - -@interface ASDefaultPlaybackButton : ASControlNode -@property (nonatomic) ASDefaultPlaybackButtonType buttonType; -@end diff --git a/submodules/AsyncDisplayKit/Source/Private/ASDefaultPlaybackButton.mm b/submodules/AsyncDisplayKit/Source/Private/ASDefaultPlaybackButton.mm deleted file mode 100644 index deda7e8716..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASDefaultPlaybackButton.mm +++ /dev/null @@ -1,84 +0,0 @@ -// -// ASDefaultPlaybackButton.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -@interface ASDefaultPlaybackButton() -{ - ASDefaultPlaybackButtonType _buttonType; -} -@end - -@implementation ASDefaultPlaybackButton -- (instancetype)init -{ - if (!(self = [super init])) { - return nil; - } - - self.opaque = NO; - - return self; -} - -- (void)setButtonType:(ASDefaultPlaybackButtonType)buttonType -{ - ASDefaultPlaybackButtonType oldType = _buttonType; - _buttonType = buttonType; - - if (oldType != _buttonType) { - [self setNeedsDisplay]; - } -} - -- (nullable NSDictionary *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer -{ - return @{ - @"buttonType" : @(self.buttonType), - @"color" : self.tintColor - }; -} - -+ (void)drawRect:(CGRect)bounds withParameters:(NSDictionary *)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing -{ - ASDefaultPlaybackButtonType buttonType = (ASDefaultPlaybackButtonType)[parameters[@"buttonType"] intValue]; - UIColor *color = parameters[@"color"]; - - CGContextRef context = UIGraphicsGetCurrentContext(); - CGContextSaveGState(context); - UIBezierPath* bezierPath = [UIBezierPath bezierPath]; - if (buttonType == ASDefaultPlaybackButtonTypePlay) { - [bezierPath moveToPoint: CGPointMake(0, 0)]; - [bezierPath addLineToPoint: CGPointMake(0, bounds.size.height)]; - [bezierPath addLineToPoint: CGPointMake(bounds.size.width, bounds.size.height/2)]; - [bezierPath addLineToPoint: CGPointMake(0, 0)]; - [bezierPath closePath]; - } else if (buttonType == ASDefaultPlaybackButtonTypePause) { - CGFloat pauseSingleLineWidth = bounds.size.width / 3.0; - [bezierPath moveToPoint: CGPointMake(0, bounds.size.height)]; - [bezierPath addLineToPoint: CGPointMake(pauseSingleLineWidth, bounds.size.height)]; - [bezierPath addLineToPoint: CGPointMake(pauseSingleLineWidth, 0)]; - [bezierPath addLineToPoint: CGPointMake(0, 0)]; - [bezierPath addLineToPoint: CGPointMake(0, bounds.size.height)]; - [bezierPath closePath]; - [bezierPath moveToPoint: CGPointMake(pauseSingleLineWidth * 2, 0)]; - [bezierPath addLineToPoint: CGPointMake(pauseSingleLineWidth * 2, bounds.size.height)]; - [bezierPath addLineToPoint: CGPointMake(bounds.size.width, bounds.size.height)]; - [bezierPath addLineToPoint: CGPointMake(bounds.size.width, 0)]; - [bezierPath addLineToPoint: CGPointMake(pauseSingleLineWidth * 2, 0)]; - [bezierPath closePath]; - } - - [color setFill]; - [bezierPath fill]; - - CGContextRestoreGState(context); -} -@end diff --git a/submodules/AsyncDisplayKit/Source/Private/ASDisplayNode+DebugTiming.h b/submodules/AsyncDisplayKit/Source/Private/ASDisplayNode+DebugTiming.h deleted file mode 100644 index f6935224a9..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASDisplayNode+DebugTiming.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// ASDisplayNode+DebugTiming.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@interface ASDisplayNode (DebugTiming) - -@property (nonatomic, readonly) NSTimeInterval debugTimeToCreateView; -@property (nonatomic, readonly) NSTimeInterval debugTimeToApplyPendingState; -@property (nonatomic, readonly) NSTimeInterval debugTimeToAddSubnodeViews; -@property (nonatomic, readonly) NSTimeInterval debugTimeForDidLoad; - -- (NSTimeInterval)debugAllCreationTime; - -@end diff --git a/submodules/AsyncDisplayKit/Source/Private/ASDisplayNode+DebugTiming.mm b/submodules/AsyncDisplayKit/Source/Private/ASDisplayNode+DebugTiming.mm deleted file mode 100644 index d9311a10d9..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASDisplayNode+DebugTiming.mm +++ /dev/null @@ -1,85 +0,0 @@ -// -// ASDisplayNode+DebugTiming.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -@implementation ASDisplayNode (DebugTiming) - -#if TIME_DISPLAYNODE_OPS -- (NSTimeInterval)debugTimeToCreateView -{ - return _debugTimeToCreateView; -} - -- (NSTimeInterval)debugTimeToApplyPendingState -{ - return _debugTimeToApplyPendingState; -} - -- (NSTimeInterval)debugTimeToAddSubnodeViews -{ - return _debugTimeToAddSubnodeViews; -} - -- (NSTimeInterval)debugTimeForDidLoad -{ - return _debugTimeForDidLoad; -} - -- (NSTimeInterval)debugAllCreationTime -{ - return self.debugTimeToCreateView + self.debugTimeToApplyPendingState + self.debugTimeToAddSubnodeViews + self.debugTimeForDidLoad; -} - -// This would over-count views that are created in the parent's didload or addsubnodesubviews, so we need to take a more basic approach -//- (NSTimeInterval)debugRecursiveAllCreationTime -//{ -// __block NSTimeInterval total = 0; -// ASDisplayNodeFindAllSubnodes(self, ^(ASDisplayNode *n){ -// total += self.debugTimeToCreateView; -// total += self.debugTimeToApplyPendingState; -// total += self.debugTimeToAddSubnodeViews; -// total += self.debugTimeForDidLoad; -// return NO; -// }); -// return total; -//} - -#else - -// These ivars are compiled out so we don't have the info available -- (NSTimeInterval)debugTimeToCreateView -{ - return -1; -} - -- (NSTimeInterval)debugTimeToApplyPendingState -{ - return -1; -} - -- (NSTimeInterval)debugTimeToAddSubnodeViews -{ - return -1; -} - -- (NSTimeInterval)debugTimeForDidLoad -{ - return -1; -} - -- (NSTimeInterval)debugAllCreationTime -{ - return -1; -} - -#endif - -@end diff --git a/submodules/AsyncDisplayKit/Source/Private/ASDisplayNodeCornerLayerDelegate.h b/submodules/AsyncDisplayKit/Source/Private/ASDisplayNodeCornerLayerDelegate.h deleted file mode 100644 index cb95e9fbf6..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASDisplayNodeCornerLayerDelegate.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// ASDisplayNodeCornerLayerDelegate.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@interface ASDisplayNodeCornerLayerDelegate : NSObject -@end diff --git a/submodules/AsyncDisplayKit/Source/Private/ASDisplayNodeCornerLayerDelegate.mm b/submodules/AsyncDisplayKit/Source/Private/ASDisplayNodeCornerLayerDelegate.mm deleted file mode 100644 index 42838baba1..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASDisplayNodeCornerLayerDelegate.mm +++ /dev/null @@ -1,19 +0,0 @@ -// -// ASDisplayNodeCornerLayerDelegate.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASDisplayNodeCornerLayerDelegate.h" - -@implementation ASDisplayNodeCornerLayerDelegate - -- (id)actionForLayer:(CALayer *)layer forKey:(NSString *)event -{ - return (id)kCFNull; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/Private/ASDisplayNodeTipState.h b/submodules/AsyncDisplayKit/Source/Private/ASDisplayNodeTipState.h deleted file mode 100644 index 1e1ef20726..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASDisplayNodeTipState.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// ASDisplayNodeTipState.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -@class ASDisplayNode, ASTipNode; - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED -@interface ASDisplayNodeTipState : NSObject - -- (instancetype)initWithNode:(ASDisplayNode *)node NS_DESIGNATED_INITIALIZER; - -- (instancetype)init NS_UNAVAILABLE; - -/// Unsafe because once the node is deallocated, we will not be able to access the tip state. -@property (nonatomic, unsafe_unretained, readonly) ASDisplayNode *node; - -/// Main-thread-only. -@property (nonatomic, nullable) ASTipNode *tipNode; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Private/ASDisplayNodeTipState.mm b/submodules/AsyncDisplayKit/Source/Private/ASDisplayNodeTipState.mm deleted file mode 100644 index b5a4231998..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASDisplayNodeTipState.mm +++ /dev/null @@ -1,25 +0,0 @@ -// -// ASDisplayNodeTipState.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASDisplayNodeTipState.h" - -@interface ASDisplayNodeTipState () -@end - -@implementation ASDisplayNodeTipState - -- (instancetype)initWithNode:(ASDisplayNode *)node -{ - if (self = [super init]) { - _node = node; - } - return self; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/Private/ASIGListAdapterBasedDataSource.h b/submodules/AsyncDisplayKit/Source/Private/ASIGListAdapterBasedDataSource.h deleted file mode 100644 index 5be2185f2f..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASIGListAdapterBasedDataSource.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// ASIGListAdapterBasedDataSource.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_IG_LIST_KIT - -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED -@interface ASIGListAdapterBasedDataSource : NSObject - -- (instancetype)initWithListAdapter:(IGListAdapter *)listAdapter collectionDelegate:(nullable id)collectionDelegate; - -@end - -#endif - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Private/ASIGListAdapterBasedDataSource.mm b/submodules/AsyncDisplayKit/Source/Private/ASIGListAdapterBasedDataSource.mm deleted file mode 100644 index 3cf77afc0a..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASIGListAdapterBasedDataSource.mm +++ /dev/null @@ -1,364 +0,0 @@ -// -// ASIGListAdapterBasedDataSource.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_IG_LIST_KIT - -#import "ASIGListAdapterBasedDataSource.h" -#import -#import - -typedef IGListSectionController ASIGSectionController; - -/// The optional methods that a class implements from ASSectionController. -/// Note: Bitfields are not supported by NSValue so we can't use them. -typedef struct { - BOOL sizeRangeForItem; - BOOL shouldBatchFetch; - BOOL beginBatchFetchWithContext; -} ASSectionControllerOverrides; - -/// The optional methods that a class implements from ASSupplementaryNodeSource. -/// Note: Bitfields are not supported by NSValue so we can't use them. -typedef struct { - BOOL sizeRangeForSupplementary; -} ASSupplementarySourceOverrides; - -@protocol ASIGSupplementaryNodeSource -@end - -@interface ASIGListAdapterBasedDataSource () -@property (nonatomic, weak, readonly) IGListAdapter *listAdapter; -@property (nonatomic, readonly) id delegate; -@property (nonatomic, readonly) id dataSource; -@property (nonatomic, weak, readonly) id collectionDelegate; - -/** - * The section controller that we will forward beginBatchFetchWithContext: to. - * Since shouldBatchFetch: is called on main, we capture the last section controller in there, - * and then we use it and clear it in beginBatchFetchWithContext: (on default queue). - * - * It is safe to use it without a lock in this limited way, since those two methods will - * never execute in parallel. - */ -@property (nonatomic, weak) ASIGSectionController *sectionControllerForBatchFetching; -@end - -@implementation ASIGListAdapterBasedDataSource - -- (instancetype)initWithListAdapter:(IGListAdapter *)listAdapter collectionDelegate:(nullable id)collectionDelegate -{ - if (self = [super init]) { -#if IG_LIST_COLLECTION_VIEW - [ASIGListAdapterBasedDataSource setASCollectionViewSuperclass]; -#endif - [ASIGListAdapterBasedDataSource configureUpdater:listAdapter.updater]; - - ASDisplayNodeAssert([listAdapter conformsToProtocol:@protocol(UICollectionViewDataSource)], @"Expected IGListAdapter to conform to UICollectionViewDataSource."); - ASDisplayNodeAssert([listAdapter conformsToProtocol:@protocol(UICollectionViewDelegateFlowLayout)], @"Expected IGListAdapter to conform to UICollectionViewDelegateFlowLayout."); - _listAdapter = listAdapter; - _collectionDelegate = collectionDelegate; - } - return self; -} - -- (id)dataSource -{ - return (id)_listAdapter; -} - -- (id)delegate -{ - return (id)_listAdapter; -} - -#pragma mark - ASCollectionDelegate - -- (void)collectionNode:(ASCollectionNode *)collectionNode didSelectItemAtIndexPath:(NSIndexPath *)indexPath -{ - [self.delegate collectionView:collectionNode.view didSelectItemAtIndexPath:indexPath]; -} - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView -{ - [self.delegate scrollViewDidScroll:scrollView]; -} - -- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView -{ - [self.delegate scrollViewWillBeginDragging:scrollView]; -} - -- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset -{ - // IGListAdapter doesn't implement scrollViewWillEndDragging yet (pending pull request), so we need this check for now. Doesn't hurt to have it anyways :) - if ([self.delegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { - [self.delegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset]; - } -} - -- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate -{ - [self.delegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate]; -} - -- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView -{ - [self.delegate scrollViewDidEndDecelerating:scrollView]; -} - -- (BOOL)shouldBatchFetchForCollectionNode:(ASCollectionNode *)collectionNode -{ - if ([_collectionDelegate respondsToSelector:@selector(shouldBatchFetchForCollectionNode:)]) { - return [_collectionDelegate shouldBatchFetchForCollectionNode:collectionNode]; - } - - NSInteger sectionCount = [self numberOfSectionsInCollectionNode:collectionNode]; - if (sectionCount == 0) { - return NO; - } - - // If they implement shouldBatchFetch, call it. Otherwise, just say YES if they implement beginBatchFetch. - ASIGSectionController *ctrl = [self sectionControllerForSection:sectionCount - 1]; - ASSectionControllerOverrides o = [ASIGListAdapterBasedDataSource overridesForSectionControllerClass:ctrl.class]; - BOOL result = (o.shouldBatchFetch ? [ctrl shouldBatchFetch] : o.beginBatchFetchWithContext); - if (result) { - self.sectionControllerForBatchFetching = ctrl; - } - return result; -} - -- (void)collectionNode:(ASCollectionNode *)collectionNode willBeginBatchFetchWithContext:(ASBatchContext *)context -{ - if ([_collectionDelegate respondsToSelector:@selector(collectionNode:willBeginBatchFetchWithContext:)]) { - [_collectionDelegate collectionNode:collectionNode willBeginBatchFetchWithContext:context]; - return; - } - - ASIGSectionController *ctrl = self.sectionControllerForBatchFetching; - self.sectionControllerForBatchFetching = nil; - [ctrl beginBatchFetchWithContext:context]; -} - -/** - * Note: It is not documented that ASCollectionNode will forward these UIKit delegate calls if they are implemented. - * It is not considered harmful to do so, and adding them to documentation will confuse most users, who should - * instead using the ASCollectionDelegate callbacks. - */ -#pragma mark - ASCollectionDelegateInterop - -- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath -{ - [self.delegate collectionView:collectionView willDisplayCell:cell forItemAtIndexPath:indexPath]; -} - -- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath -{ - [self.delegate collectionView:collectionView didEndDisplayingCell:cell forItemAtIndexPath:indexPath]; -} - -#pragma mark - ASCollectionDelegateFlowLayout - -- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode sizeRangeForHeaderInSection:(NSInteger)section -{ - id src = [self supplementaryElementSourceForSection:section]; - if ([ASIGListAdapterBasedDataSource overridesForSupplementarySourceClass:[src class]].sizeRangeForSupplementary) { - return [src sizeRangeForSupplementaryElementOfKind:UICollectionElementKindSectionHeader atIndex:0]; - } else { - return ASSizeRangeZero; - } -} - -- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode sizeRangeForFooterInSection:(NSInteger)section -{ - id src = [self supplementaryElementSourceForSection:section]; - if ([ASIGListAdapterBasedDataSource overridesForSupplementarySourceClass:[src class]].sizeRangeForSupplementary) { - return [src sizeRangeForSupplementaryElementOfKind:UICollectionElementKindSectionFooter atIndex:0]; - } else { - return ASSizeRangeZero; - } -} - -- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section -{ - return [self.delegate collectionView:collectionView layout:collectionViewLayout insetForSectionAtIndex:section]; -} - -- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section -{ - return [self.delegate collectionView:collectionView layout:collectionViewLayout minimumLineSpacingForSectionAtIndex:section]; -} - -- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section -{ - return [self.delegate collectionView:collectionView layout:collectionViewLayout minimumInteritemSpacingForSectionAtIndex:section]; -} - -#pragma mark - ASCollectionDataSource - -- (NSInteger)collectionNode:(ASCollectionNode *)collectionNode numberOfItemsInSection:(NSInteger)section -{ - return [self.dataSource collectionView:collectionNode.view numberOfItemsInSection:section]; -} - -- (NSInteger)numberOfSectionsInCollectionNode:(ASCollectionNode *)collectionNode -{ - return [self.dataSource numberOfSectionsInCollectionView:collectionNode.view]; -} - -- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath -{ - ASIGSectionController *ctrl = [self sectionControllerForSection:indexPath.section]; - ASDisplayNodeAssert([ctrl respondsToSelector:@selector(nodeBlockForItemAtIndex:)], @"Expected section controller to respond to to %@. Controller: %@", NSStringFromSelector(@selector(nodeBlockForItemAtIndex:)), ctrl); - return [ctrl nodeBlockForItemAtIndex:indexPath.item]; -} - -- (ASCellNode *)collectionNode:(ASCollectionNode *)collectionNode nodeForItemAtIndexPath:(NSIndexPath *)indexPath -{ - ASIGSectionController *ctrl = [self sectionControllerForSection:indexPath.section]; - ASDisplayNodeAssert([ctrl respondsToSelector:@selector(nodeForItemAtIndex:)], @"Expected section controller to respond to to %@. Controller: %@", NSStringFromSelector(@selector(nodeForItemAtIndex:)), ctrl); - return [ctrl nodeForItemAtIndex:indexPath.item]; -} - -- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode constrainedSizeForItemAtIndexPath:(NSIndexPath *)indexPath -{ - ASIGSectionController *ctrl = [self sectionControllerForSection:indexPath.section]; - if ([ASIGListAdapterBasedDataSource overridesForSectionControllerClass:ctrl.class].sizeRangeForItem) { - return [ctrl sizeRangeForItemAtIndex:indexPath.item]; - } else { - return ASSizeRangeUnconstrained; - } -} - -- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - id ctrl = [self supplementaryElementSourceForSection:indexPath.section]; - ASDisplayNodeAssert([ctrl respondsToSelector:@selector(nodeBlockForSupplementaryElementOfKind:atIndex:)], @"Expected section controller to respond to to %@. Controller: %@", NSStringFromSelector(@selector(nodeBlockForSupplementaryElementOfKind:atIndex:)), ctrl); - return [ctrl nodeBlockForSupplementaryElementOfKind:kind atIndex:indexPath.item]; -} - -- (ASCellNode *)collectionNode:(ASCollectionNode *)collectionNode nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - id ctrl = [self supplementaryElementSourceForSection:indexPath.section]; - ASDisplayNodeAssert([ctrl respondsToSelector:@selector(nodeForSupplementaryElementOfKind:atIndex:)], @"Expected section controller to respond to to %@. Controller: %@", NSStringFromSelector(@selector(nodeForSupplementaryElementOfKind:atIndex:)), ctrl); - return [ctrl nodeForSupplementaryElementOfKind:kind atIndex:indexPath.item]; -} - -- (NSArray *)collectionNode:(ASCollectionNode *)collectionNode supplementaryElementKindsInSection:(NSInteger)section -{ - return [[self supplementaryElementSourceForSection:section] supportedElementKinds]; -} - -#pragma mark - ASCollectionDataSourceInterop - -- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(nonnull NSIndexPath *)indexPath -{ - return [self.dataSource collectionView:collectionView cellForItemAtIndexPath:indexPath]; -} - -- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - return [self.dataSource collectionView:collectionView viewForSupplementaryElementOfKind:kind atIndexPath:indexPath]; -} - -+ (BOOL)dequeuesCellsForNodeBackedItems -{ - return YES; -} - -#pragma mark - Helpers - -- (id)supplementaryElementSourceForSection:(NSInteger)section -{ - ASIGSectionController *ctrl = [self sectionControllerForSection:section]; - id src = (id)ctrl.supplementaryViewSource; - ASDisplayNodeAssert(src == nil || [src conformsToProtocol:@protocol(ASSupplementaryNodeSource)], @"Supplementary view source should conform to %@", NSStringFromProtocol(@protocol(ASSupplementaryNodeSource))); - return src; -} - -- (ASIGSectionController *)sectionControllerForSection:(NSInteger)section -{ - id object = [_listAdapter objectAtSection:section]; - ASIGSectionController *ctrl = (ASIGSectionController *)[_listAdapter sectionControllerForObject:object]; - ASDisplayNodeAssert([ctrl conformsToProtocol:@protocol(ASSectionController)], @"Expected section controller to conform to %@. Controller: %@", NSStringFromProtocol(@protocol(ASSectionController)), ctrl); - return ctrl; -} - -/// If needed, set ASCollectionView's superclass to IGListCollectionView (IGListKit < 3.0). -#if IG_LIST_COLLECTION_VIEW -+ (void)setASCollectionViewSuperclass -{ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - class_setSuperclass([ASCollectionView class], [IGListCollectionView class]); - }); -#pragma clang diagnostic pop -} -#endif - -/// Ensure updater won't call reloadData on us. -+ (void)configureUpdater:(id)updater -{ - // Cast to NSObject will be removed after https://github.com/Instagram/IGListKit/pull/435 - if ([(id)updater isKindOfClass:[IGListAdapterUpdater class]]) { - [(IGListAdapterUpdater *)updater setAllowsBackgroundReloading:NO]; - } else { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSLog(@"WARNING: Use of non-%@ updater with AsyncDisplayKit is discouraged. Updater: %@", NSStringFromClass([IGListAdapterUpdater class]), updater); - }); - } -} - -+ (ASSupplementarySourceOverrides)overridesForSupplementarySourceClass:(Class)c -{ - static NSCache *cache; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - cache = [[NSCache alloc] init]; - }); - NSValue *obj = [cache objectForKey:c]; - ASSupplementarySourceOverrides o; - if (obj == nil) { - o.sizeRangeForSupplementary = [c instancesRespondToSelector:@selector(sizeRangeForSupplementaryElementOfKind:atIndex:)]; - obj = [NSValue valueWithBytes:&o objCType:@encode(ASSupplementarySourceOverrides)]; - [cache setObject:obj forKey:c]; - } else { - [obj getValue:&o]; - } - return o; -} - -+ (ASSectionControllerOverrides)overridesForSectionControllerClass:(Class)c -{ - static NSCache *cache; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - cache = [[NSCache alloc] init]; - }); - NSValue *obj = [cache objectForKey:c]; - ASSectionControllerOverrides o; - if (obj == nil) { - o.sizeRangeForItem = [c instancesRespondToSelector:@selector(sizeRangeForItemAtIndex:)]; - o.beginBatchFetchWithContext = [c instancesRespondToSelector:@selector(beginBatchFetchWithContext:)]; - o.shouldBatchFetch = [c instancesRespondToSelector:@selector(shouldBatchFetch)]; - obj = [NSValue valueWithBytes:&o objCType:@encode(ASSectionControllerOverrides)]; - [cache setObject:obj forKey:c]; - } else { - [obj getValue:&o]; - } - return o; -} - -@end - -#endif // AS_IG_LIST_KIT diff --git a/submodules/AsyncDisplayKit/Source/Private/ASImageNode+AnimatedImagePrivate.h b/submodules/AsyncDisplayKit/Source/Private/ASImageNode+AnimatedImagePrivate.h deleted file mode 100644 index 528b39beb3..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASImageNode+AnimatedImagePrivate.h +++ /dev/null @@ -1,47 +0,0 @@ -// -// ASImageNode+AnimatedImagePrivate.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#define ASAnimatedImageDefaultRunLoopMode NSRunLoopCommonModes - -@interface ASImageNode () -{ -#ifndef MINIMAL_ASDK - AS::Mutex _displayLinkLock; - id _animatedImage; - BOOL _animatedImagePaused; - NSString *_animatedImageRunLoopMode; - CADisplayLink *_displayLink; - NSUInteger _lastSuccessfulFrameIndex; - - //accessed on main thread only - CFTimeInterval _playHead; - NSUInteger _playedLoops; -#endif -} - -@property (nonatomic) CFTimeInterval lastDisplayLinkFire; - -@end - -#ifndef MINIMAL_ASDK -@interface ASImageNode (AnimatedImagePrivate) - -- (void)_locked_setAnimatedImage:(id )animatedImage; - -@end - -@interface ASImageNode (AnimatedImageInvalidation) - -- (void)invalidateAnimatedImage; - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Private/ASImageNode+CGExtras.h b/submodules/AsyncDisplayKit/Source/Private/ASImageNode+CGExtras.h deleted file mode 100644 index 01b546443b..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASImageNode+CGExtras.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// ASImageNode+CGExtras.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -/** - @abstract Decides how to scale and crop an image to fit in the provided size, while not wasting memory by upscaling images - @param sourceImageSize The size of the encoded image. - @param boundsSize The bounds in which the image will be displayed. - @param contentMode The mode that defines how image will be scaled and cropped to fit. Supported values are UIViewContentModeScaleToAspectFill and UIViewContentModeScaleToAspectFit. - @param cropRect A rectangle that is to be featured by the cropped image. The rectangle is specified as a "unit rectangle," using fractions of the source image's width and height, e.g. CGRectMake(0.5, 0, 0.5, 1.0) will feature the full right half a photo. If the cropRect is empty, the contentMode will be used to determine the drawRect's size, and only the cropRect's origin will be used for positioning. - @param forceUpscaling A boolean that indicates you would *not* like the backing size to be downscaled if the image is smaller than the destination size. Setting this to YES will result in higher memory usage when images are smaller than their destination. - @param forcedSize A CGSize, that if non-CGSizeZero, indicates that the backing size should be forcedSize and not calculated based on boundsSize. - @discussion If the image is smaller than the size and UIViewContentModeScaleToAspectFill is specified, we suggest the input size so it will be efficiently upscaled on the GPU by the displaying layer at composite time. - */ -AS_EXTERN void ASCroppedImageBackingSizeAndDrawRectInBounds(CGSize sourceImageSize, - CGSize boundsSize, - UIViewContentMode contentMode, - CGRect cropRect, - BOOL forceUpscaling, - CGSize forcedSize, - CGSize *outBackingSize, - CGRect *outDrawRect - ); diff --git a/submodules/AsyncDisplayKit/Source/Private/ASImageNode+CGExtras.mm b/submodules/AsyncDisplayKit/Source/Private/ASImageNode+CGExtras.mm deleted file mode 100644 index c4f9fdcbc9..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASImageNode+CGExtras.mm +++ /dev/null @@ -1,123 +0,0 @@ -// -// ASImageNode+CGExtras.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -// TODO rewrite these to be closer to the intended use -- take UIViewContentMode as param, CGRect destinationBounds, CGSize sourceSize. -static CGSize _ASSizeFillWithAspectRatio(CGFloat aspectRatio, CGSize constraints); -static CGSize _ASSizeFitWithAspectRatio(CGFloat aspectRatio, CGSize constraints); - -static CGSize _ASSizeFillWithAspectRatio(CGFloat sizeToScaleAspectRatio, CGSize destinationSize) -{ - CGFloat destinationAspectRatio = destinationSize.width / destinationSize.height; - if (sizeToScaleAspectRatio > destinationAspectRatio) { - return CGSizeMake(destinationSize.height * sizeToScaleAspectRatio, destinationSize.height); - } else { - return CGSizeMake(destinationSize.width, round(destinationSize.width / sizeToScaleAspectRatio)); - } -} - -static CGSize _ASSizeFitWithAspectRatio(CGFloat aspectRatio, CGSize constraints) -{ - CGFloat constraintAspectRatio = constraints.width / constraints.height; - if (aspectRatio > constraintAspectRatio) { - return CGSizeMake(constraints.width, constraints.width / aspectRatio); - } else { - return CGSizeMake(constraints.height * aspectRatio, constraints.height); - } -} - -void ASCroppedImageBackingSizeAndDrawRectInBounds(CGSize sourceImageSize, - CGSize boundsSize, - UIViewContentMode contentMode, - CGRect cropRect, - BOOL forceUpscaling, - CGSize forcedSize, - CGSize *outBackingSize, - CGRect *outDrawRect - ) -{ - - size_t destinationWidth = boundsSize.width; - size_t destinationHeight = boundsSize.height; - - // Often, an image is too low resolution to completely fill the width and height provided. - // Per the API contract as commented in the header, we will adjust input parameters (destinationWidth, destinationHeight) to ensure that the image is not upscaled on the CPU. - CGFloat boundsAspectRatio = (CGFloat)destinationWidth / (CGFloat)destinationHeight; - - CGSize scaledSizeForImage = sourceImageSize; - BOOL cropToRectDimensions = !CGRectIsEmpty(cropRect); - - if (cropToRectDimensions) { - scaledSizeForImage = CGSizeMake(boundsSize.width / cropRect.size.width, boundsSize.height / cropRect.size.height); - } else { - if (contentMode == UIViewContentModeScaleAspectFill) - scaledSizeForImage = _ASSizeFillWithAspectRatio(boundsAspectRatio, sourceImageSize); - else if (contentMode == UIViewContentModeScaleAspectFit) - scaledSizeForImage = _ASSizeFitWithAspectRatio(boundsAspectRatio, sourceImageSize); - } - - // If fitting the desired aspect ratio to the image size actually results in a larger buffer, use the input values. - // However, if there is a pixel savings (e.g. we would have to upscale the image), override the function arguments. - if (CGSizeEqualToSize(CGSizeZero, forcedSize) == NO) { - destinationWidth = (size_t)round(forcedSize.width); - destinationHeight = (size_t)round(forcedSize.height); - } else if (forceUpscaling == NO && (scaledSizeForImage.width * scaledSizeForImage.height) < (destinationWidth * destinationHeight)) { - destinationWidth = (size_t)round(scaledSizeForImage.width); - destinationHeight = (size_t)round(scaledSizeForImage.height); - if (destinationWidth == 0 || destinationHeight == 0) { - *outBackingSize = CGSizeZero; - *outDrawRect = CGRectZero; - return; - } - } - - // Figure out the scaled size within the destination bounds. - CGFloat sourceImageAspectRatio = sourceImageSize.width / sourceImageSize.height; - CGSize scaledSizeForDestination = CGSizeMake(destinationWidth, destinationHeight); - - if (cropToRectDimensions) { - scaledSizeForDestination = CGSizeMake(boundsSize.width / cropRect.size.width, boundsSize.height / cropRect.size.height); - } else { - if (contentMode == UIViewContentModeScaleAspectFill) - scaledSizeForDestination = _ASSizeFillWithAspectRatio(sourceImageAspectRatio, scaledSizeForDestination); - else if (contentMode == UIViewContentModeScaleAspectFit) - scaledSizeForDestination = _ASSizeFitWithAspectRatio(sourceImageAspectRatio, scaledSizeForDestination); - } - - // Figure out the rectangle into which to draw the image. - CGRect drawRect = CGRectZero; - if (cropToRectDimensions) { - drawRect = CGRectMake(-cropRect.origin.x * scaledSizeForDestination.width, - -cropRect.origin.y * scaledSizeForDestination.height, - scaledSizeForDestination.width, - scaledSizeForDestination.height); - } else { - // We want to obey the origin of cropRect in aspect-fill mode. - if (contentMode == UIViewContentModeScaleAspectFill) { - drawRect = CGRectMake(((destinationWidth - scaledSizeForDestination.width) * cropRect.origin.x), - ((destinationHeight - scaledSizeForDestination.height) * cropRect.origin.y), - scaledSizeForDestination.width, - scaledSizeForDestination.height); - - } - // And otherwise just center it. - else { - drawRect = CGRectMake(((destinationWidth - scaledSizeForDestination.width) / 2.0), - ((destinationHeight - scaledSizeForDestination.height) / 2.0), - scaledSizeForDestination.width, - scaledSizeForDestination.height); - } - } - - *outDrawRect = drawRect; - *outBackingSize = CGSizeMake(destinationWidth, destinationHeight); -} diff --git a/submodules/AsyncDisplayKit/Source/Private/ASImageNode+Private.h b/submodules/AsyncDisplayKit/Source/Private/ASImageNode+Private.h deleted file mode 100644 index 8de78c8784..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASImageNode+Private.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// ASImageNode+Private.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#pragma once - -@interface ASImageNode (Private) - -- (void)_locked_setImage:(UIImage *)image; -- (UIImage *)_locked_Image; - -@end diff --git a/submodules/AsyncDisplayKit/Source/Private/ASLayerBackingTipProvider.h b/submodules/AsyncDisplayKit/Source/Private/ASLayerBackingTipProvider.h deleted file mode 100644 index 0cc3d03b61..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASLayerBackingTipProvider.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// ASLayerBackingTipProvider.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASTipProvider.h" -#import - -#if AS_ENABLE_TIPS - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED -@interface ASLayerBackingTipProvider : ASTipProvider - -@end - -NS_ASSUME_NONNULL_END - -#endif // AS_ENABLE_TIPS diff --git a/submodules/AsyncDisplayKit/Source/Private/ASLayerBackingTipProvider.mm b/submodules/AsyncDisplayKit/Source/Private/ASLayerBackingTipProvider.mm deleted file mode 100644 index 19d1850ab7..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASLayerBackingTipProvider.mm +++ /dev/null @@ -1,45 +0,0 @@ -// -// ASLayerBackingTipProvider.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASLayerBackingTipProvider.h" - -#if AS_ENABLE_TIPS - -#import -#import -#import -#import -#import - -@implementation ASLayerBackingTipProvider - -- (ASTip *)tipForNode:(ASDisplayNode *)node -{ - // Already layer-backed. - if (node.layerBacked) { - return nil; - } - - // TODO: Avoid revisiting nodes we already visited - ASDisplayNode *failNode = ASDisplayNodeFindFirstNode(node, ^BOOL(ASDisplayNode * _Nonnull node) { - return !node.supportsLayerBacking; - }); - if (failNode != nil) { - return nil; - } - - ASTip *result = [[ASTip alloc] initWithNode:node - kind:ASTipKindEnableLayerBacking - format:@"Enable layer backing to improve performance"]; - return result; -} - -@end - -#endif // AS_ENABLE_TIPS diff --git a/submodules/AsyncDisplayKit/Source/Private/ASMutableElementMap.h b/submodules/AsyncDisplayKit/Source/Private/ASMutableElementMap.h deleted file mode 100644 index ed4f0b5ff4..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASMutableElementMap.h +++ /dev/null @@ -1,66 +0,0 @@ -// -// ASMutableElementMap.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@class ASSection, ASCollectionElement, _ASHierarchyChangeSet; - -/** - * This mutable version will be removed in the future. It's only here now to keep the diff small - * as we port data controller to use ASElementMap. - */ -AS_SUBCLASSING_RESTRICTED -@interface ASMutableElementMap : NSObject - -- (instancetype)init __unavailable; - -- (instancetype)initWithSections:(NSArray *)sections items:(ASCollectionElementTwoDimensionalArray *)items supplementaryElements:(ASSupplementaryElementDictionary *)supplementaryElements; - -- (void)insertSection:(ASSection *)section atIndex:(NSInteger)index; - -- (void)removeAllSections; - -/// Only modifies the array of ASSection * objects -- (void)removeSectionsAtIndexes:(NSIndexSet *)indexes; - -- (void)removeAllElements; - -- (void)removeItemsAtIndexPaths:(NSArray *)indexPaths; - -- (void)removeSectionsOfItems:(NSIndexSet *)itemSections; - -- (void)removeSupplementaryElementsAtIndexPaths:(NSArray *)indexPaths kind:(NSString *)kind; - -- (void)insertEmptySectionsOfItemsAtIndexes:(NSIndexSet *)sections; - -- (void)insertElement:(ASCollectionElement *)element atIndexPath:(NSIndexPath *)indexPath; - -/** - * Update the index paths for all supplementary elements to account for section-level - * deletes, moves, inserts. This must be called before adding new supplementary elements. - * - * This also deletes any supplementary elements in deleted sections. - */ -- (void)migrateSupplementaryElementsWithSectionMapping:(ASIntegerMap *)mapping; - -@end - -@interface ASElementMap (MutableCopying) -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Private/ASMutableElementMap.mm b/submodules/AsyncDisplayKit/Source/Private/ASMutableElementMap.mm deleted file mode 100644 index 73798be119..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASMutableElementMap.mm +++ /dev/null @@ -1,149 +0,0 @@ -// -// ASMutableElementMap.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -#import -#import -#import -#import -#import - -typedef NSMutableArray *> ASMutableCollectionElementTwoDimensionalArray; - -typedef NSMutableDictionary *> ASMutableSupplementaryElementDictionary; - -@implementation ASMutableElementMap { - ASMutableSupplementaryElementDictionary *_supplementaryElements; - NSMutableArray *_sections; - ASMutableCollectionElementTwoDimensionalArray *_sectionsOfItems; -} - -- (instancetype)initWithSections:(NSArray *)sections items:(ASCollectionElementTwoDimensionalArray *)items supplementaryElements:(ASSupplementaryElementDictionary *)supplementaryElements -{ - if (self = [super init]) { - _sections = [sections mutableCopy]; - _sectionsOfItems = (ASMutableCollectionElementTwoDimensionalArray *)ASTwoDimensionalArrayDeepMutableCopy(items); - _supplementaryElements = [ASMutableElementMap deepMutableCopyOfElementsDictionary:supplementaryElements]; - } - return self; -} - -- (id)copyWithZone:(NSZone *)zone -{ - return [[ASElementMap alloc] initWithSections:_sections items:_sectionsOfItems supplementaryElements:_supplementaryElements]; -} - -- (void)removeAllSections -{ - [_sections removeAllObjects]; -} - -- (void)insertSection:(ASSection *)section atIndex:(NSInteger)index -{ - [_sections insertObject:section atIndex:index]; -} - -- (void)removeItemsAtIndexPaths:(NSArray *)indexPaths -{ - ASDeleteElementsInTwoDimensionalArrayAtIndexPaths(_sectionsOfItems, indexPaths); -} - -- (void)removeSectionsAtIndexes:(NSIndexSet *)indexes -{ - [_sections removeObjectsAtIndexes:indexes]; -} - -- (void)removeSupplementaryElementsAtIndexPaths:(NSArray *)indexPaths kind:(NSString *)kind -{ - [_supplementaryElements[kind] removeObjectsForKeys:indexPaths]; -} - -- (void)removeAllElements -{ - [_sectionsOfItems removeAllObjects]; - [_supplementaryElements removeAllObjects]; -} - -- (void)removeSectionsOfItems:(NSIndexSet *)itemSections -{ - [_sectionsOfItems removeObjectsAtIndexes:itemSections]; -} - -- (void)insertEmptySectionsOfItemsAtIndexes:(NSIndexSet *)sections -{ - [sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { - [_sectionsOfItems insertObject:[[NSMutableArray alloc] init] atIndex:idx]; - }]; -} - -- (void)insertElement:(ASCollectionElement *)element atIndexPath:(NSIndexPath *)indexPath -{ - NSString *kind = element.supplementaryElementKind; - if (kind == nil) { - [_sectionsOfItems[indexPath.section] insertObject:element atIndex:indexPath.item]; - } else { - NSMutableDictionary *supplementariesForKind = _supplementaryElements[kind]; - if (supplementariesForKind == nil) { - supplementariesForKind = [[NSMutableDictionary alloc] init]; - _supplementaryElements[kind] = supplementariesForKind; - } - supplementariesForKind[indexPath] = element; - } -} - -- (void)migrateSupplementaryElementsWithSectionMapping:(ASIntegerMap *)mapping -{ - // Fast-path, no section changes. - if (mapping == ASIntegerMap.identityMap) { - return; - } - - // For each element kind, - [_supplementaryElements enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSMutableDictionary * _Nonnull supps, BOOL * _Nonnull stop) { - - // For each index path of that kind, move entries into a new dictionary. - // Note: it's tempting to update the dictionary in-place but because of the likely collision between old and new index paths, - // subtle bugs are possible. Note that this process is rare (only on section-level updates), - // that this work is done off-main, and that the typical supplementary element use case is just 1-per-section (header). - NSMutableDictionary *newSupps = [[NSMutableDictionary alloc] init]; - [supps enumerateKeysAndObjectsUsingBlock:^(NSIndexPath * _Nonnull oldIndexPath, ASCollectionElement * _Nonnull obj, BOOL * _Nonnull stop) { - NSInteger oldSection = oldIndexPath.section; - NSInteger newSection = [mapping integerForKey:oldSection]; - - if (oldSection == newSection) { - // Index path stayed the same, just copy it over. - newSupps[oldIndexPath] = obj; - } else if (newSection != NSNotFound) { - // Section index changed, move it. - NSIndexPath *newIndexPath = [NSIndexPath indexPathForItem:oldIndexPath.item inSection:newSection]; - newSupps[newIndexPath] = obj; - } - }]; - [supps setDictionary:newSupps]; - }]; -} - -#pragma mark - Helpers - -+ (ASMutableSupplementaryElementDictionary *)deepMutableCopyOfElementsDictionary:(ASSupplementaryElementDictionary *)originalDict -{ - NSMutableDictionary *deepCopy = [[NSMutableDictionary alloc] initWithCapacity:originalDict.count]; - [originalDict enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSDictionary * _Nonnull obj, BOOL * _Nonnull stop) { - deepCopy[key] = [obj mutableCopy]; - }]; - - return deepCopy; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Private/ASNetworkImageLoadInfo+Private.h b/submodules/AsyncDisplayKit/Source/Private/ASNetworkImageLoadInfo+Private.h deleted file mode 100644 index 36e423b535..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASNetworkImageLoadInfo+Private.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// ASNetworkImageLoadInfo+Private.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface ASNetworkImageLoadInfo () - -- (instancetype)initWithURL:(NSURL *)url - sourceType:(ASNetworkImageSourceType)sourceType - downloadIdentifier:(nullable id)downloadIdentifier - userInfo:(nullable id)userInfo; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Private/ASSection.h b/submodules/AsyncDisplayKit/Source/Private/ASSection.h deleted file mode 100644 index 92b6a17170..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASSection.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// ASSection.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import - -@protocol ASSectionContext; - -NS_ASSUME_NONNULL_BEGIN - -/** - * An object representing the metadata for a section of elements in a collection. - * - * Its sectionID is namespaced to the data controller that created the section. - * - * These are useful for tracking the movement & lifetime of sections, independent of - * their contents. - */ -AS_SUBCLASSING_RESTRICTED -@interface ASSection : NSObject - -@property (readonly) NSInteger sectionID; -@property (nullable, readonly) id context; - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithSectionID:(NSInteger)sectionID context:(nullable id)context NS_DESIGNATED_INITIALIZER; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Private/ASSection.mm b/submodules/AsyncDisplayKit/Source/Private/ASSection.mm deleted file mode 100644 index a151774bd4..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASSection.mm +++ /dev/null @@ -1,29 +0,0 @@ -// -// ASSection.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import - -@implementation ASSection - -- (instancetype)initWithSectionID:(NSInteger)sectionID context:(id)context -{ - self = [super init]; - if (self) { - _sectionID = sectionID; - _context = context; - } - return self; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Private/ASTableView+Undeprecated.h b/submodules/AsyncDisplayKit/Source/Private/ASTableView+Undeprecated.h deleted file mode 100644 index 46c54368a6..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASTableView+Undeprecated.h +++ /dev/null @@ -1,298 +0,0 @@ -// -// ASTableView+Undeprecated.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * Currently our public table API is on @c ASTableNode and the @c ASTableView - * API is deprecated, but the implementations still live in the view. - * - * This category lets us avoid deprecation warnings everywhere internally. - * In the future, the ASTableView public API will be eliminated and so will this file. - */ -@interface ASTableView (Undeprecated) - -@property (nullable, nonatomic, weak) id asyncDelegate; -@property (nullable, nonatomic, weak) id asyncDataSource; -@property (nonatomic) UIEdgeInsets contentInset; -@property (nonatomic) CGPoint contentOffset; -@property (nonatomic) BOOL automaticallyAdjustsContentOffset; -@property (nonatomic) BOOL inverted; -@property (nullable, nonatomic, readonly) NSArray *indexPathsForVisibleRows; -@property (nullable, nonatomic, readonly) NSArray *indexPathsForSelectedRows; -@property (nullable, nonatomic, readonly) NSIndexPath *indexPathForSelectedRow; - -/** - * The number of screens left to scroll before the delegate -tableView:beginBatchFetchingWithContext: is called. - * - * Defaults to two screenfuls. - */ -@property (nonatomic) CGFloat leadingScreensForBatching; - -/** - * Initializer. - * - * @param frame A rectangle specifying the initial location and size of the table view in its superview’€™s coordinates. - * The frame of the table view changes as table cells are added and deleted. - * - * @param style A constant that specifies the style of the table view. See UITableViewStyle for descriptions of valid constants. - */ -- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style; - -/** - * Tuning parameters for a range type in full mode. - * - * @param rangeType The range type to get the tuning parameters for. - * - * @return A tuning parameter value for the given range type in full mode. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType AS_WARN_UNUSED_RESULT; - -/** - * Set the tuning parameters for a range type in full mode. - * - * @param tuningParameters The tuning parameters to store for a range type. - * @param rangeType The range type to set the tuning parameters for. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType; - -/** - * Tuning parameters for a range type in the specified mode. - * - * @param rangeMode The range mode to get the running parameters for. - * @param rangeType The range type to get the tuning parameters for. - * - * @return A tuning parameter value for the given range type in the given mode. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType AS_WARN_UNUSED_RESULT; - -/** - * Set the tuning parameters for a range type in the specified mode. - * - * @param tuningParameters The tuning parameters to store for a range type. - * @param rangeMode The range mode to set the running parameters for. - * @param rangeType The range type to set the tuning parameters for. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; - -- (nullable __kindof UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath; - -/** - * Scrolls the table to the given row. - * - * @param indexPath The index path of the row. - * @param scrollPosition Where the row should end up after the scroll. - * @param animated Whether the scroll should be animated or not. - */ -- (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated; - -- (void)selectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition; - -- (nullable NSIndexPath *)indexPathForRowAtPoint:(CGPoint)point; - -- (nullable NSArray *)indexPathsForRowsInRect:(CGRect)rect; - -/** - * Similar to -visibleCells. - * - * @return an array containing the cell nodes being displayed on screen. - */ -- (NSArray *)visibleNodes AS_WARN_UNUSED_RESULT; - -/** - * Similar to -indexPathForCell:. - * - * @param cellNode a cellNode part of the table view - * - * @return an indexPath for this cellNode - */ -- (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode AS_WARN_UNUSED_RESULT; - -/** - * Reload everything from scratch, destroying the working range and all cached nodes. - * - * @param completion block to run on completion of asynchronous loading or nil. If supplied, the block is run on - * the main thread. - * @warning This method is substantially more expensive than UITableView's version. - */ --(void)reloadDataWithCompletion:(void (^ _Nullable)(void))completion; - -/** - * Reload everything from scratch, destroying the working range and all cached nodes. - * - * @warning This method is substantially more expensive than UITableView's version. - */ -- (void)reloadData; - -/** - * Triggers a relayout of all nodes. - * - * @discussion This method invalidates and lays out every cell node in the table view. - */ -- (void)relayoutItems; - -/** - * Begins a series of method calls that insert, delete, select, or reload rows and sections of the table view, with animation enabled and no completion block. - * - * @discussion You call this method to bracket a series of method calls that ends with endUpdates and that consists of operations - * to insert, delete, select, and reload rows and sections of the table view. When you call endUpdates, ASTableView begins animating - * the operations simultaneously. It's important to remember that the ASTableView will be processing the updates asynchronously after this call is completed. - * - * @warning This method must be called from the main thread. - */ -- (void)beginUpdates; - -/** - * Concludes a series of method calls that insert, delete, select, or reload rows and sections of the table view, with animation enabled and no completion block. - * - * @discussion You call this method to bracket a series of method calls that begins with beginUpdates and that consists of operations - * to insert, delete, select, and reload rows and sections of the table view. When you call endUpdates, ASTableView begins animating - * the operations simultaneously. It's important to remember that the ASTableView will be processing the updates asynchronously after this call is completed. - * - * @warning This method is must be called from the main thread. - */ -- (void)endUpdates; - -/** - * Concludes a series of method calls that insert, delete, select, or reload rows and sections of the table view. - * You call this method to bracket a series of method calls that begins with beginUpdates and that consists of operations - * to insert, delete, select, and reload rows and sections of the table view. When you call endUpdates, ASTableView begins animating - * the operations simultaneously. This method is must be called from the main thread. It's important to remember that the ASTableView will - * be processing the updates asynchronously after this call and are not guaranteed to be reflected in the ASTableView until - * the completion block is executed. - * - * @param animated NO to disable all animations. - * @param completion A completion handler block to execute when all of the operations are finished. This block takes a single - * Boolean parameter that contains the value YES if all of the related animations completed successfully or - * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. - */ -- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^ _Nullable)(BOOL completed))completion; - -/** - * Blocks execution of the main thread until all section and row updates are committed. This method must be called from the main thread. - */ -- (void)waitUntilAllUpdatesAreCommitted; - -/** - * Inserts one or more sections, with an option to animate the insertion. - * - * @param sections An index set that specifies the sections to insert. - * - * @param animation A constant that indicates how the insertion is to be animated. See UITableViewRowAnimation. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation; - -/** - * Deletes one or more sections, with an option to animate the deletion. - * - * @param sections An index set that specifies the sections to delete. - * - * @param animation A constant that indicates how the deletion is to be animated. See UITableViewRowAnimation. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation; - -/** - * Reloads the specified sections using a given animation effect. - * - * @param sections An index set that specifies the sections to reload. - * - * @param animation A constant that indicates how the reloading is to be animated. See UITableViewRowAnimation. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation; - -/** - * Moves a section to a new location. - * - * @param section The index of the section to move. - * - * @param newSection The index that is the destination of the move for the section. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection; - -/** - * Inserts rows at the locations identified by an array of index paths, with an option to animate the insertion. - * - * @param indexPaths An array of NSIndexPath objects, each representing a row index and section index that together identify a row. - * - * @param animation A constant that indicates how the insertion is to be animated. See UITableViewRowAnimation. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation; - -/** - * Deletes the rows specified by an array of index paths, with an option to animate the deletion. - * - * @param indexPaths An array of NSIndexPath objects identifying the rows to delete. - * - * @param animation A constant that indicates how the deletion is to be animated. See UITableViewRowAnimation. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation; - -/** - * Reloads the specified rows using a given animation effect. - * - * @param indexPaths An array of NSIndexPath objects identifying the rows to reload. - * - * @param animation A constant that indicates how the reloading is to be animated. See UITableViewRowAnimation. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation; - -/** - * Moves the row at a specified location to a destination location. - * - * @param indexPath The index path identifying the row to move. - * - * @param newIndexPath The index path that is the destination of the move for the row. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath; - -- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated; - -@end -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Private/ASTip.h b/submodules/AsyncDisplayKit/Source/Private/ASTip.h deleted file mode 100644 index 5ac6ac18bc..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASTip.h +++ /dev/null @@ -1,49 +0,0 @@ -// -// ASTip.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -#if AS_ENABLE_TIPS - -NS_ASSUME_NONNULL_BEGIN - -@class ASDisplayNode; - -typedef NS_ENUM (NSInteger, ASTipKind) { - ASTipKindEnableLayerBacking -}; - -AS_SUBCLASSING_RESTRICTED -@interface ASTip : NSObject - -- (instancetype)initWithNode:(ASDisplayNode *)node - kind:(ASTipKind)kind - format:(NSString *)format, ... NS_FORMAT_FUNCTION(3, 4); - -/** - * The kind of tip this is. - */ -@property (nonatomic, readonly) ASTipKind kind; - -/** - * The node that this tip applies to. - */ -@property (nonatomic, readonly) ASDisplayNode *node; - -/** - * The text to show the user. - */ -@property (nonatomic, readonly) NSString *text; - -@end - -NS_ASSUME_NONNULL_END - -#endif // AS_ENABLE_TIPS diff --git a/submodules/AsyncDisplayKit/Source/Private/ASTip.mm b/submodules/AsyncDisplayKit/Source/Private/ASTip.mm deleted file mode 100644 index af1d299221..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASTip.mm +++ /dev/null @@ -1,35 +0,0 @@ -// -// ASTip.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_ENABLE_TIPS - -#import - -@implementation ASTip - -- (instancetype)initWithNode:(ASDisplayNode *)node - kind:(ASTipKind)kind - format:(NSString *)format, ... -{ - if (self = [super init]) { - _node = node; - _kind = kind; - va_list args; - va_start(args, format); - _text = [[NSString alloc] initWithFormat:format arguments:args]; - va_end(args); - } - return self; -} - -@end - -#endif // AS_ENABLE_TIPS diff --git a/submodules/AsyncDisplayKit/Source/Private/ASTipNode.h b/submodules/AsyncDisplayKit/Source/Private/ASTipNode.h deleted file mode 100644 index d01637d869..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASTipNode.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// ASTipNode.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -#if AS_ENABLE_TIPS - -@class ASTip; - -NS_ASSUME_NONNULL_BEGIN - -/** - * ASTipNode will send these up the responder chain. - */ -@protocol ASTipNodeActions -- (void)didTapTipNode:(id)sender; -@end - -AS_SUBCLASSING_RESTRICTED -@interface ASTipNode : ASControlNode - -- (instancetype)initWithTip:(ASTip *)tip NS_DESIGNATED_INITIALIZER; - -- (instancetype)init NS_UNAVAILABLE; - -@property (nonatomic, readonly) ASTip *tip; - -@end - -NS_ASSUME_NONNULL_END - -#endif // AS_ENABLE_TIPS diff --git a/submodules/AsyncDisplayKit/Source/Private/ASTipNode.mm b/submodules/AsyncDisplayKit/Source/Private/ASTipNode.mm deleted file mode 100644 index dcca908b8a..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASTipNode.mm +++ /dev/null @@ -1,28 +0,0 @@ -// -// ASTipNode.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASTipNode.h" - -#if AS_ENABLE_TIPS - -@implementation ASTipNode - -- (instancetype)initWithTip:(ASTip *)tip -{ - if (self = [super init]) { - self.backgroundColor = [UIColor colorWithRed:0 green:0.7 blue:0.2 alpha:0.3]; - _tip = tip; - [self addTarget:nil action:@selector(didTapTipNode:) forControlEvents:ASControlNodeEventTouchUpInside]; - } - return self; -} - -@end - -#endif // AS_ENABLE_TIPS diff --git a/submodules/AsyncDisplayKit/Source/Private/ASTipProvider.h b/submodules/AsyncDisplayKit/Source/Private/ASTipProvider.h deleted file mode 100644 index e2aba6c5d9..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASTipProvider.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// ASTipProvider.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -#if AS_ENABLE_TIPS - -@class ASDisplayNode, ASTip; - -NS_ASSUME_NONNULL_BEGIN - -/** - * An abstract superclass for all tip providers. - */ -@interface ASTipProvider : NSObject - -/** - * The provider looks at the node's current situation and - * generates a tip, if any, to add to the node. - * - * Subclasses must override this. - */ -- (nullable ASTip *)tipForNode:(ASDisplayNode *)node; - -@end - -@interface ASTipProvider (Lookup) - -@property (class, nonatomic, copy, readonly) NSArray<__kindof ASTipProvider *> *all; - -@end - -NS_ASSUME_NONNULL_END - -#endif // AS_ENABLE_TIPS diff --git a/submodules/AsyncDisplayKit/Source/Private/ASTipProvider.mm b/submodules/AsyncDisplayKit/Source/Private/ASTipProvider.mm deleted file mode 100644 index 237e83cda0..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASTipProvider.mm +++ /dev/null @@ -1,43 +0,0 @@ -// -// ASTipProvider.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_ENABLE_TIPS - -#import - -// Concrete classes -#import - -@implementation ASTipProvider - -- (ASTip *)tipForNode:(ASDisplayNode *)node -{ - ASDisplayNodeFailAssert(@"Subclasses must override %@", NSStringFromSelector(_cmd)); - return nil; -} - -@end - -@implementation ASTipProvider (Lookup) - -+ (NSArray *)all -{ - static NSArray *providers; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - providers = @[ [ASLayerBackingTipProvider new] ]; - }); - return providers; -} - -@end - -#endif // AS_ENABLE_TIPS diff --git a/submodules/AsyncDisplayKit/Source/Private/ASTipsController.h b/submodules/AsyncDisplayKit/Source/Private/ASTipsController.h deleted file mode 100644 index fceeb92f60..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASTipsController.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// ASTipsController.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -#if AS_ENABLE_TIPS - -@class ASDisplayNode; - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED -@interface ASTipsController : NSObject - -/** - * The shared tip controller instance. - */ -@property (class, readonly) ASTipsController *shared; - -#pragma mark - Node Event Hooks - -/** - * Informs the controller that the sender did enter the visible range. - * - * The controller will run a pass with its tip providers, adding tips as needed. - */ -- (void)nodeDidAppear:(ASDisplayNode *)node; - -@end - -NS_ASSUME_NONNULL_END - -#endif // AS_ENABLE_TIPS diff --git a/submodules/AsyncDisplayKit/Source/Private/ASTipsController.mm b/submodules/AsyncDisplayKit/Source/Private/ASTipsController.mm deleted file mode 100644 index acc592130c..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASTipsController.mm +++ /dev/null @@ -1,185 +0,0 @@ -// -// ASTipsController.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_ENABLE_TIPS - -#import -#import -#import -#import -#import -#import - -@interface ASTipsController () - -/// Nil on init, updates to most recent visible window. -@property (nonatomic) UIWindow *appVisibleWindow; - -/// Nil until an application window has become visible. -@property (nonatomic) ASTipsWindow *tipWindow; - -/// Main-thread-only. -@property (nonatomic, readonly) NSMapTable *nodeToTipStates; - -@property (nonatomic) NSMutableArray *nodesThatAppearedDuringRunLoop; - -@end - -@implementation ASTipsController - -#pragma mark - Singleton - -+ (void)load -{ - [NSNotificationCenter.defaultCenter addObserver:self.shared - selector:@selector(windowDidBecomeVisibleWithNotification:) - name:UIWindowDidBecomeVisibleNotification - object:nil]; -} - -+ (ASTipsController *)shared -{ - static ASTipsController *ctrl; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - ctrl = [[ASTipsController alloc] init]; - }); - return ctrl; -} - -#pragma mark - Lifecycle - -- (instancetype)init -{ - ASDisplayNodeAssertMainThread(); - if (self = [super init]) { - _nodeToTipStates = [NSMapTable mapTableWithKeyOptions:(NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPointerPersonality) valueOptions:NSPointerFunctionsStrongMemory]; - _nodesThatAppearedDuringRunLoop = [NSMutableArray array]; - } - return self; -} - -#pragma mark - Event Handling - -- (void)nodeDidAppear:(ASDisplayNode *)node -{ - ASDisplayNodeAssertMainThread(); - // If they disabled tips on this class, bail. - if (![[node class] enableTips]) { - return; - } - - // If this node appeared in some other window (like our tips window) ignore it. - if (ASFindWindowOfLayer(node.layer) != self.appVisibleWindow) { - return; - } - - [_nodesThatAppearedDuringRunLoop addObject:node]; -} - -// If this is a main window, start watching it and clear out our tip window. -- (void)windowDidBecomeVisibleWithNotification:(NSNotification *)notification -{ - ASDisplayNodeAssertMainThread(); - UIWindow *window = notification.object; - - // If this is the same window we're already watching, bail. - if (window == self.appVisibleWindow) { - return; - } - - // Ignore windows that are not at the normal level or have empty bounds - if (window.windowLevel != UIWindowLevelNormal || CGRectIsEmpty(window.bounds)) { - return; - } - - self.appVisibleWindow = window; - - // Create the tip window if needed. - [self createTipWindowIfNeededWithFrame:window.bounds]; - - // Clear out our tip window and reset our states. - self.tipWindow.mainWindow = window; - [_nodeToTipStates removeAllObjects]; -} - -- (void)runLoopDidTick -{ - NSArray *nodes = [_nodesThatAppearedDuringRunLoop copy]; - [_nodesThatAppearedDuringRunLoop removeAllObjects]; - - // Go through the old array, removing any that have tips but aren't still visible. - for (ASDisplayNode *node in [_nodeToTipStates copy]) { - if (!node.visible) { - [_nodeToTipStates removeObjectForKey:node]; - } - } - - for (ASDisplayNode *node in nodes) { - // Get the tip state for the node. - ASDisplayNodeTipState *tipState = [_nodeToTipStates objectForKey:node]; - - // If the node already has a tip, bail. This could change. - if (tipState.tipNode != nil) { - return; - } - - for (ASTipProvider *provider in ASTipProvider.all) { - ASTip *tip = [provider tipForNode:node]; - if (!tip) { continue; } - - if (!tipState) { - tipState = [self createTipStateForNode:node]; - } - tipState.tipNode = [[ASTipNode alloc] initWithTip:tip]; - } - } - self.tipWindow.nodeToTipStates = _nodeToTipStates; - [self.tipWindow setNeedsLayout]; -} - -#pragma mark - Internal - -- (void)createTipWindowIfNeededWithFrame:(CGRect)tipWindowFrame -{ - // Lots of property accesses, but simple safe code, only run once. - if (self.tipWindow == nil) { - self.tipWindow = [[ASTipsWindow alloc] initWithFrame:tipWindowFrame]; - self.tipWindow.hidden = NO; - [self setupRunLoopObserver]; - } -} - -/** - * In order to keep the UI updated, the tips controller registers a run loop observer. - * Before the transaction commit happens, the tips controller calls -setNeedsLayout - * on the view controller's view. It will then layout the main window, and then update the frames - * for tip nodes accordingly. - */ -- (void)setupRunLoopObserver -{ - CFRunLoopObserverRef o = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { - [self runLoopDidTick]; - }); - CFRunLoopAddObserver(CFRunLoopGetMain(), o, kCFRunLoopCommonModes); -} - -- (ASDisplayNodeTipState *)createTipStateForNode:(ASDisplayNode *)node -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeTipState *tipState = [[ASDisplayNodeTipState alloc] initWithNode:node]; - [_nodeToTipStates setObject:tipState forKey:node]; - return tipState; -} - -@end - -#endif // AS_ENABLE_TIPS diff --git a/submodules/AsyncDisplayKit/Source/Private/ASTipsWindow.h b/submodules/AsyncDisplayKit/Source/Private/ASTipsWindow.h deleted file mode 100644 index fab52510fc..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASTipsWindow.h +++ /dev/null @@ -1,36 +0,0 @@ -// -// ASTipsWindow.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -#if AS_ENABLE_TIPS - -@class ASDisplayNode, ASDisplayNodeTipState; - -NS_ASSUME_NONNULL_BEGIN - -/** - * A window that shows tips. This was originally meant to be a view controller - * but UIKit will not manage view controllers in non-key windows correctly AT ALL - * as of the time of this writing. - */ -AS_SUBCLASSING_RESTRICTED -@interface ASTipsWindow : UIWindow - -/// The main application window that the tips are tracking. -@property (nonatomic, weak) UIWindow *mainWindow; - -@property (nonatomic, copy, nullable) NSMapTable *nodeToTipStates; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Private/ASTipsWindow.mm b/submodules/AsyncDisplayKit/Source/Private/ASTipsWindow.mm deleted file mode 100644 index 010932613a..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASTipsWindow.mm +++ /dev/null @@ -1,98 +0,0 @@ -// -// ASTipsWindow.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#if AS_ENABLE_TIPS - -#import -#import -#import -#import - -@interface ASTipsWindow () -@property (nonatomic, readonly) ASDisplayNode *node; -@end - -@implementation ASTipsWindow - -- (instancetype)initWithFrame:(CGRect)frame -{ - if (self = [super initWithFrame:frame]) { - /** - * UIKit throws an exception if you don't add a root view controller to a window, - * but if the window isn't key, then it doesn't manage the root view controller correctly! - * - * So we set a dummy root view controller and hide it. - */ - self.rootViewController = [UIViewController new]; - self.rootViewController.view.hidden = YES; - - _node = [[ASDisplayNode alloc] init]; - [self addSubnode:_node]; - - self.windowLevel = UIWindowLevelNormal + 1; - self.opaque = NO; - } - return self; -} - -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event -{ - UIView *result = [super hitTest:point withEvent:event]; - // Ignore touches unless they hit one of my node's subnodes - if (result == _node.view) { - return nil; - } - return result; -} - -- (void)setMainWindow:(UIWindow *)mainWindow -{ - _mainWindow = mainWindow; - for (ASDisplayNode *node in _node.subnodes) { - [node removeFromSupernode]; - } -} - -- (void)didTapTipNode:(ASTipNode *)tipNode -{ - ASDisplayNode.tipDisplayBlock(tipNode.tip.node, tipNode.tip.text); -} - -- (void)layoutSubviews -{ - [super layoutSubviews]; - _node.frame = self.bounds; - - // Ensure the main window is laid out first. - [self.mainWindow layoutIfNeeded]; - - NSMutableSet *tipNodesToRemove = [NSMutableSet setWithArray:_node.subnodes]; - for (ASDisplayNodeTipState *tipState in [_nodeToTipStates objectEnumerator]) { - ASDisplayNode *node = tipState.node; - ASTipNode *tipNode = tipState.tipNode; - [tipNodesToRemove removeObject:tipNode]; - CGRect rect = node.bounds; - rect = [node.view convertRect:rect toView:nil]; - rect = [self convertRect:rect fromView:nil]; - tipNode.frame = rect; - if (tipNode.supernode != _node) { - [_node addSubnode:tipNode]; - } - } - - // Clean up any tip nodes whose target nodes have disappeared. - for (ASTipNode *tipNode in tipNodesToRemove) { - [tipNode removeFromSupernode]; - } -} - -@end - -#endif // AS_ENABLE_TIPS diff --git a/submodules/AsyncDisplayKit/Source/Private/ASTwoDimensionalArrayUtils.h b/submodules/AsyncDisplayKit/Source/Private/ASTwoDimensionalArrayUtils.h deleted file mode 100644 index 6a3c9bb5ce..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASTwoDimensionalArrayUtils.h +++ /dev/null @@ -1,46 +0,0 @@ -// -// ASTwoDimensionalArrayUtils.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * Helper functions for two-dimensional array, where the objects of the root array are each arrays. - */ - -/** - * Deep mutable copy of an array that contains arrays, which contain objects. It will go one level deep into the array to copy. - * This method is substantially faster than the generalized version, e.g. about 10x faster, so use it whenever it fits the need. - */ -AS_EXTERN NSMutableArray *ASTwoDimensionalArrayDeepMutableCopy(NSArray *array) AS_WARN_UNUSED_RESULT; - -/** - * Delete the elements of the mutable two-dimensional array at given index paths – sorted in descending order! - */ -AS_EXTERN void ASDeleteElementsInTwoDimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths); - -/** - * Return all the index paths of a two-dimensional array, in ascending order. - */ -AS_EXTERN NSArray *ASIndexPathsForTwoDimensionalArray(NSArray* twoDimensionalArray) AS_WARN_UNUSED_RESULT; - -/** - * Return all the elements of a two-dimensional array, in ascending order. - */ -AS_EXTERN NSArray *ASElementsInTwoDimensionalArray(NSArray* twoDimensionalArray) AS_WARN_UNUSED_RESULT; - -/** - * Attempt to get the object at the given index path. Returns @c nil if the index path is out of bounds. - */ -AS_EXTERN id _Nullable ASGetElementInTwoDimensionalArray(NSArray *array, NSIndexPath *indexPath) AS_WARN_UNUSED_RESULT; - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Private/ASTwoDimensionalArrayUtils.mm b/submodules/AsyncDisplayKit/Source/Private/ASTwoDimensionalArrayUtils.mm deleted file mode 100644 index 67267b7a94..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/ASTwoDimensionalArrayUtils.mm +++ /dev/null @@ -1,122 +0,0 @@ -// -// ASTwoDimensionalArrayUtils.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import -#import - -#import - -// Import UIKit to get [NSIndexPath indexPathForItem:inSection:] which uses -// tagged pointers. -#import - -#pragma mark - Public Methods - -NSMutableArray *ASTwoDimensionalArrayDeepMutableCopy(NSArray *array) -{ - NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:array.count]; - NSInteger i = 0; - for (NSArray *subarray in array) { - ASDisplayNodeCAssert([subarray isKindOfClass:[NSArray class]], @"This function expects NSArray *"); - newArray[i++] = [subarray mutableCopy]; - } - return newArray; -} - -void ASDeleteElementsInTwoDimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths) -{ - if (indexPaths.count == 0) { - return; - } - -#if ASDISPLAYNODE_ASSERTIONS_ENABLED - NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(asdk_inverseCompare:)]; - ASDisplayNodeCAssert([sortedIndexPaths isEqualToArray:indexPaths], @"Expected array of index paths to be sorted in descending order."); -#endif - - /** - * It is tempting to do something clever here and collect indexes into ranges or NSIndexSets - * but deep down, __NSArrayM only implements removeObjectAtIndex: and so doing all that extra - * work ends up running the same code. - */ - for (NSIndexPath *indexPath in indexPaths) { - NSInteger section = indexPath.section; - if (section >= mutableArray.count) { - ASDisplayNodeCFailAssert(@"Invalid section index %ld – only %ld sections", (long)section, (long)mutableArray.count); - continue; - } - - NSMutableArray *subarray = mutableArray[section]; - NSInteger item = indexPath.item; - if (item >= subarray.count) { - ASDisplayNodeCFailAssert(@"Invalid item index %ld – only %ld items in section %ld", (long)item, (long)subarray.count, (long)section); - continue; - } - [subarray removeObjectAtIndex:item]; - } -} - -NSArray *ASIndexPathsForTwoDimensionalArray(NSArray * twoDimensionalArray) -{ - NSInteger sectionCount = twoDimensionalArray.count; - NSInteger counts[sectionCount]; - NSInteger totalCount = 0; - NSInteger i = 0; - for (NSArray *subarray in twoDimensionalArray) { - NSInteger count = subarray.count; - counts[i++] = count; - totalCount += count; - } - - // Count could be huge. Use a reserved vector rather than VLA (stack.) - std::vector indexPaths; - indexPaths.reserve(totalCount); - for (NSInteger i = 0; i < sectionCount; i++) { - for (NSInteger j = 0; j < counts[i]; j++) { - indexPaths.push_back([NSIndexPath indexPathForItem:j inSection:i]); - } - } - return [NSArray arrayByTransferring:indexPaths.data() count:totalCount]; -} - -NSArray *ASElementsInTwoDimensionalArray(NSArray * twoDimensionalArray) -{ - NSInteger totalCount = 0; - for (NSArray *subarray in twoDimensionalArray) { - totalCount += subarray.count; - } - - std::vector elements; - elements.reserve(totalCount); - for (NSArray *subarray in twoDimensionalArray) { - for (id object in subarray) { - elements.push_back(object); - } - } - return [NSArray arrayByTransferring:elements.data() count:totalCount]; -} - -id ASGetElementInTwoDimensionalArray(NSArray *array, NSIndexPath *indexPath) -{ - ASDisplayNodeCAssertNotNil(indexPath, @"Expected non-nil index path"); - ASDisplayNodeCAssert(indexPath.length == 2, @"Expected index path of length 2. Index path: %@", indexPath); - NSInteger section = indexPath.section; - if (array.count <= section) { - return nil; - } - - NSArray *innerArray = array[section]; - NSInteger item = indexPath.item; - if (innerArray.count <= item) { - return nil; - } - return innerArray[item]; -} diff --git a/submodules/AsyncDisplayKit/Source/Private/Layout/ASStackLayoutSpecUtilities.h b/submodules/AsyncDisplayKit/Source/Private/Layout/ASStackLayoutSpecUtilities.h deleted file mode 100644 index 6c708e0408..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/Layout/ASStackLayoutSpecUtilities.h +++ /dev/null @@ -1,135 +0,0 @@ -// -// ASStackLayoutSpecUtilities.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -typedef struct { - ASStackLayoutDirection direction; - CGFloat spacing; - ASStackLayoutJustifyContent justifyContent; - ASStackLayoutAlignItems alignItems; - ASStackLayoutFlexWrap flexWrap; - ASStackLayoutAlignContent alignContent; - CGFloat lineSpacing; -} ASStackLayoutSpecStyle; - -inline CGFloat stackDimension(const ASStackLayoutDirection direction, const CGSize size) -{ - return (direction == ASStackLayoutDirectionVertical) ? size.height : size.width; -} - -inline CGFloat crossDimension(const ASStackLayoutDirection direction, const CGSize size) -{ - return (direction == ASStackLayoutDirectionVertical) ? size.width : size.height; -} - -inline BOOL compareCrossDimension(const ASStackLayoutDirection direction, const CGSize a, const CGSize b) -{ - return crossDimension(direction, a) < crossDimension(direction, b); -} - -inline CGPoint directionPoint(const ASStackLayoutDirection direction, const CGFloat stack, const CGFloat cross) -{ - return (direction == ASStackLayoutDirectionVertical) ? CGPointMake(cross, stack) : CGPointMake(stack, cross); -} - -inline CGSize directionSize(const ASStackLayoutDirection direction, const CGFloat stack, const CGFloat cross) -{ - return (direction == ASStackLayoutDirectionVertical) ? CGSizeMake(cross, stack) : CGSizeMake(stack, cross); -} - -inline void setStackValueToPoint(const ASStackLayoutDirection direction, const CGFloat stack, CGPoint &point) { - (direction == ASStackLayoutDirectionVertical) ? (point.y = stack) : (point.x = stack); -} - -inline ASSizeRange directionSizeRange(const ASStackLayoutDirection direction, - const CGFloat stackMin, - const CGFloat stackMax, - const CGFloat crossMin, - const CGFloat crossMax) -{ - return {directionSize(direction, stackMin, crossMin), directionSize(direction, stackMax, crossMax)}; -} - -inline ASStackLayoutAlignItems alignment(ASStackLayoutAlignSelf childAlignment, ASStackLayoutAlignItems stackAlignment) -{ - switch (childAlignment) { - case ASStackLayoutAlignSelfCenter: - return ASStackLayoutAlignItemsCenter; - case ASStackLayoutAlignSelfEnd: - return ASStackLayoutAlignItemsEnd; - case ASStackLayoutAlignSelfStart: - return ASStackLayoutAlignItemsStart; - case ASStackLayoutAlignSelfStretch: - return ASStackLayoutAlignItemsStretch; - case ASStackLayoutAlignSelfAuto: - default: - return stackAlignment; - } -} - -inline ASStackLayoutAlignItems alignment(ASHorizontalAlignment alignment, ASStackLayoutAlignItems defaultAlignment) -{ - switch (alignment) { - case ASHorizontalAlignmentLeft: - return ASStackLayoutAlignItemsStart; - case ASHorizontalAlignmentMiddle: - return ASStackLayoutAlignItemsCenter; - case ASHorizontalAlignmentRight: - return ASStackLayoutAlignItemsEnd; - case ASHorizontalAlignmentNone: - default: - return defaultAlignment; - } -} - -inline ASStackLayoutAlignItems alignment(ASVerticalAlignment alignment, ASStackLayoutAlignItems defaultAlignment) -{ - switch (alignment) { - case ASVerticalAlignmentTop: - return ASStackLayoutAlignItemsStart; - case ASVerticalAlignmentCenter: - return ASStackLayoutAlignItemsCenter; - case ASVerticalAlignmentBottom: - return ASStackLayoutAlignItemsEnd; - case ASVerticalAlignmentNone: - default: - return defaultAlignment; - } -} - -inline ASStackLayoutJustifyContent justifyContent(ASHorizontalAlignment alignment, ASStackLayoutJustifyContent defaultJustifyContent) -{ - switch (alignment) { - case ASHorizontalAlignmentLeft: - return ASStackLayoutJustifyContentStart; - case ASHorizontalAlignmentMiddle: - return ASStackLayoutJustifyContentCenter; - case ASHorizontalAlignmentRight: - return ASStackLayoutJustifyContentEnd; - case ASHorizontalAlignmentNone: - default: - return defaultJustifyContent; - } -} - -inline ASStackLayoutJustifyContent justifyContent(ASVerticalAlignment alignment, ASStackLayoutJustifyContent defaultJustifyContent) -{ - switch (alignment) { - case ASVerticalAlignmentTop: - return ASStackLayoutJustifyContentStart; - case ASVerticalAlignmentCenter: - return ASStackLayoutJustifyContentCenter; - case ASVerticalAlignmentBottom: - return ASStackLayoutJustifyContentEnd; - case ASVerticalAlignmentNone: - default: - return defaultJustifyContent; - } -} diff --git a/submodules/AsyncDisplayKit/Source/Private/Layout/ASStackPositionedLayout.h b/submodules/AsyncDisplayKit/Source/Private/Layout/ASStackPositionedLayout.h deleted file mode 100644 index 103ec3a280..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/Layout/ASStackPositionedLayout.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// ASStackPositionedLayout.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - -/** Represents a set of laid out and positioned stack layout children. */ -struct ASStackPositionedLayout { - const std::vector items; - /** Final size of the stack */ - const CGSize size; - - /** Given an unpositioned layout, computes the positions each child should be placed at. */ - static ASStackPositionedLayout compute(const ASStackUnpositionedLayout &unpositionedLayout, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &constrainedSize); -}; diff --git a/submodules/AsyncDisplayKit/Source/Private/Layout/ASStackPositionedLayout.mm b/submodules/AsyncDisplayKit/Source/Private/Layout/ASStackPositionedLayout.mm deleted file mode 100644 index 727db5d2dd..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/Layout/ASStackPositionedLayout.mm +++ /dev/null @@ -1,186 +0,0 @@ -// -// ASStackPositionedLayout.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import - -#import -#import -#import -#import - -static CGFloat crossOffsetForItem(const ASStackLayoutSpecItem &item, - const ASStackLayoutSpecStyle &style, - const CGFloat crossSize, - const CGFloat baseline) -{ - switch (alignment(item.child.style.alignSelf, style.alignItems)) { - case ASStackLayoutAlignItemsEnd: - return crossSize - crossDimension(style.direction, item.layout.size); - case ASStackLayoutAlignItemsCenter: - return ASFloorPixelValue((crossSize - crossDimension(style.direction, item.layout.size)) / 2); - case ASStackLayoutAlignItemsBaselineFirst: - case ASStackLayoutAlignItemsBaselineLast: - return baseline - ASStackUnpositionedLayout::baselineForItem(style, item); - case ASStackLayoutAlignItemsStart: - case ASStackLayoutAlignItemsStretch: - case ASStackLayoutAlignItemsNotSet: - return 0; - } -} - -static void crossOffsetAndSpacingForEachLine(const std::size_t numOfLines, - const CGFloat crossViolation, - ASStackLayoutAlignContent alignContent, - CGFloat &offset, - CGFloat &spacing) -{ - ASDisplayNodeCAssertTrue(numOfLines > 0); - - // Handle edge cases - if (alignContent == ASStackLayoutAlignContentSpaceBetween && (crossViolation < kViolationEpsilon || numOfLines == 1)) { - alignContent = ASStackLayoutAlignContentStart; - } else if (alignContent == ASStackLayoutAlignContentSpaceAround && (crossViolation < kViolationEpsilon || numOfLines == 1)) { - alignContent = ASStackLayoutAlignContentCenter; - } - - offset = 0; - spacing = 0; - - switch (alignContent) { - case ASStackLayoutAlignContentCenter: - offset = crossViolation / 2; - break; - case ASStackLayoutAlignContentEnd: - offset = crossViolation; - break; - case ASStackLayoutAlignContentSpaceBetween: - // Spacing between the items, no spaces at the edges, evenly distributed - spacing = crossViolation / (numOfLines - 1); - break; - case ASStackLayoutAlignContentSpaceAround: { - // Spacing between items are twice the spacing on the edges - CGFloat spacingUnit = crossViolation / (numOfLines * 2); - offset = spacingUnit; - spacing = spacingUnit * 2; - break; - } - case ASStackLayoutAlignContentStart: - case ASStackLayoutAlignContentStretch: - break; - } -} - -static void stackOffsetAndSpacingForEachItem(const std::size_t numOfItems, - const CGFloat stackViolation, - ASStackLayoutJustifyContent justifyContent, - CGFloat &offset, - CGFloat &spacing) -{ - ASDisplayNodeCAssertTrue(numOfItems > 0); - - // Handle edge cases - if (justifyContent == ASStackLayoutJustifyContentSpaceBetween && (stackViolation < kViolationEpsilon || numOfItems == 1)) { - justifyContent = ASStackLayoutJustifyContentStart; - } else if (justifyContent == ASStackLayoutJustifyContentSpaceAround && (stackViolation < kViolationEpsilon || numOfItems == 1)) { - justifyContent = ASStackLayoutJustifyContentCenter; - } - - offset = 0; - spacing = 0; - - switch (justifyContent) { - case ASStackLayoutJustifyContentCenter: - offset = stackViolation / 2; - break; - case ASStackLayoutJustifyContentEnd: - offset = stackViolation; - break; - case ASStackLayoutJustifyContentSpaceBetween: - // Spacing between the items, no spaces at the edges, evenly distributed - spacing = stackViolation / (numOfItems - 1); - break; - case ASStackLayoutJustifyContentSpaceAround: { - // Spacing between items are twice the spacing on the edges - CGFloat spacingUnit = stackViolation / (numOfItems * 2); - offset = spacingUnit; - spacing = spacingUnit * 2; - break; - } - case ASStackLayoutJustifyContentStart: - break; - } -} - -static void positionItemsInLine(const ASStackUnpositionedLine &line, - const ASStackLayoutSpecStyle &style, - const CGPoint &startingPoint, - const CGFloat stackSpacing) -{ - CGPoint p = startingPoint; - BOOL first = YES; - - for (const auto &item : line.items) { - p = p + directionPoint(style.direction, item.child.style.spacingBefore, 0); - if (!first) { - p = p + directionPoint(style.direction, style.spacing + stackSpacing, 0); - } - first = NO; - item.layout.position = p + directionPoint(style.direction, 0, crossOffsetForItem(item, style, line.crossSize, line.baseline)); - - p = p + directionPoint(style.direction, stackDimension(style.direction, item.layout.size) + item.child.style.spacingAfter, 0); - } -} - -ASStackPositionedLayout ASStackPositionedLayout::compute(const ASStackUnpositionedLayout &layout, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &sizeRange) -{ - const auto &lines = layout.lines; - if (lines.empty()) { - return {}; - } - - const auto numOfLines = lines.size(); - const auto direction = style.direction; - const auto alignContent = style.alignContent; - const auto lineSpacing = style.lineSpacing; - const auto justifyContent = style.justifyContent; - const auto crossViolation = ASStackUnpositionedLayout::computeCrossViolation(layout.crossDimensionSum, style, sizeRange); - CGFloat crossOffset; - CGFloat crossSpacing; - crossOffsetAndSpacingForEachLine(numOfLines, crossViolation, alignContent, crossOffset, crossSpacing); - - std::vector positionedItems; - CGPoint p = directionPoint(direction, 0, crossOffset); - BOOL first = YES; - for (const auto &line : lines) { - if (!first) { - p = p + directionPoint(direction, 0, crossSpacing + lineSpacing); - } - first = NO; - - const auto &items = line.items; - const auto stackViolation = ASStackUnpositionedLayout::computeStackViolation(line.stackDimensionSum, style, sizeRange); - CGFloat stackOffset; - CGFloat stackSpacing; - stackOffsetAndSpacingForEachItem(items.size(), stackViolation, justifyContent, stackOffset, stackSpacing); - - setStackValueToPoint(direction, stackOffset, p); - positionItemsInLine(line, style, p, stackSpacing); - std::move(items.begin(), items.end(), std::back_inserter(positionedItems)); - - p = p + directionPoint(direction, -stackOffset, line.crossSize); - } - - const CGSize finalSize = directionSize(direction, layout.stackDimensionSum, layout.crossDimensionSum); - return {std::move(positionedItems), ASSizeRangeClamp(sizeRange, finalSize)}; -} diff --git a/submodules/AsyncDisplayKit/Source/Private/Layout/ASStackUnpositionedLayout.h b/submodules/AsyncDisplayKit/Source/Private/Layout/ASStackUnpositionedLayout.h deleted file mode 100644 index 989b4c2f11..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/Layout/ASStackUnpositionedLayout.h +++ /dev/null @@ -1,73 +0,0 @@ -// -// ASStackUnpositionedLayout.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import -#import - -/** The threshold that determines if a violation has actually occurred. */ -AS_EXTERN CGFloat const kViolationEpsilon; - -struct ASStackLayoutSpecChild { - /** The original source child. */ - id element; - /** Style object of element. */ - ASLayoutElementStyle *style; - /** Size object of the element */ - ASLayoutElementSize size; -}; - -struct ASStackLayoutSpecItem { - /** The original source child. */ - ASStackLayoutSpecChild child; - /** The proposed layout or nil if no is calculated yet. */ - ASLayout *layout; -}; - -struct ASStackUnpositionedLine { - /** The set of proposed children in this line, each contains child layout, not yet positioned. */ - std::vector items; - /** The total size of the children in the stack dimension, including all spacing. */ - CGFloat stackDimensionSum; - /** The size in the cross dimension */ - CGFloat crossSize; - /** The baseline of the stack which baseline aligned children should align to */ - CGFloat baseline; -}; - -/** Represents a set of stack layout children that have their final layout computed, but are not yet positioned. */ -struct ASStackUnpositionedLayout { - /** The set of proposed lines, each contains child layouts, not yet positioned. */ - const std::vector lines; - /** - * In a single line stack (e.g no wrao), this is the total size of the children in the stack dimension, including all spacing. - * In a multi-line stack, this is the largest stack dimension among lines. - */ - const CGFloat stackDimensionSum; - const CGFloat crossDimensionSum; - - /** Given a set of children, computes the unpositioned layouts for those children. */ - static ASStackUnpositionedLayout compute(const std::vector &children, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &sizeRange, - const BOOL concurrent); - - static CGFloat baselineForItem(const ASStackLayoutSpecStyle &style, - const ASStackLayoutSpecItem &l); - - static CGFloat computeStackViolation(const CGFloat stackDimensionSum, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &sizeRange); - - static CGFloat computeCrossViolation(const CGFloat crossDimensionSum, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &sizeRange); -}; diff --git a/submodules/AsyncDisplayKit/Source/Private/Layout/ASStackUnpositionedLayout.mm b/submodules/AsyncDisplayKit/Source/Private/Layout/ASStackUnpositionedLayout.mm deleted file mode 100644 index 47141a7f7b..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/Layout/ASStackUnpositionedLayout.mm +++ /dev/null @@ -1,758 +0,0 @@ -// -// ASStackUnpositionedLayout.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import - -#import -#import -#import - -CGFloat const kViolationEpsilon = 0.01; - -static CGFloat resolveCrossDimensionMaxForStretchChild(const ASStackLayoutSpecStyle &style, - const ASStackLayoutSpecChild &child, - const CGFloat stackMax, - const CGFloat crossMax) -{ - // stretched children may have a cross direction max that is smaller than the minimum size constraint of the parent. - const CGFloat computedMax = (style.direction == ASStackLayoutDirectionVertical ? - ASLayoutElementSizeResolve(child.style.size, ASLayoutElementParentSizeUndefined).max.width : - ASLayoutElementSizeResolve(child.style.size, ASLayoutElementParentSizeUndefined).max.height); - return computedMax == INFINITY ? crossMax : computedMax; -} - -static CGFloat resolveCrossDimensionMinForStretchChild(const ASStackLayoutSpecStyle &style, - const ASStackLayoutSpecChild &child, - const CGFloat stackMax, - const CGFloat crossMin) -{ - // stretched children will have a cross dimension of at least crossMin, unless they explicitly define a child size - // that is smaller than the constraint of the parent. - return (style.direction == ASStackLayoutDirectionVertical ? - ASLayoutElementSizeResolve(child.style.size, ASLayoutElementParentSizeUndefined).min.width : - ASLayoutElementSizeResolve(child.style.size, ASLayoutElementParentSizeUndefined).min.height) ?: crossMin; -} - -/** - Sizes the child given the parameters specified, and returns the computed layout. - */ -static ASLayout *crossChildLayout(const ASStackLayoutSpecChild &child, - const ASStackLayoutSpecStyle &style, - const CGFloat stackMin, - const CGFloat stackMax, - const CGFloat crossMin, - const CGFloat crossMax, - const CGSize parentSize) -{ - const ASStackLayoutAlignItems alignItems = alignment(child.style.alignSelf, style.alignItems); - // stretched children will have a cross dimension of at least crossMin - const CGFloat childCrossMin = (alignItems == ASStackLayoutAlignItemsStretch ? - resolveCrossDimensionMinForStretchChild(style, child, stackMax, crossMin) : - 0); - const CGFloat childCrossMax = (alignItems == ASStackLayoutAlignItemsStretch ? - resolveCrossDimensionMaxForStretchChild(style, child, stackMax, crossMax) : - crossMax); - const ASSizeRange childSizeRange = directionSizeRange(style.direction, stackMin, stackMax, childCrossMin, childCrossMax); - ASLayout *layout = [child.element layoutThatFits:childSizeRange parentSize:parentSize]; - ASDisplayNodeCAssertNotNil(layout, @"ASLayout returned from -layoutThatFits:parentSize: must not be nil: %@", child.element); - return layout ? : [ASLayout layoutWithLayoutElement:child.element size:{0, 0}]; -} - -static void dispatchApplyIfNeeded(size_t iterationCount, BOOL forced, void(^work)(size_t i)) -{ - if (iterationCount == 0) { - return; - } - - if (iterationCount == 1) { - work(0); - return; - } - - // TODO Once the locking situation in ASDisplayNode has improved, always dispatch if on main - if (forced == NO) { - for (size_t i = 0; i < iterationCount; i++) { - work(i); - } - return; - } - - dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - ASDispatchApply(iterationCount, queue, 0, work); -} - -/** - Computes the consumed cross dimension length for the given vector of lines and stacking style. - - Cross Dimension - +---------------------> - +--------+ +--------+ +--------+ +---------+ - Vertical |Vertical| |Vertical| |Vertical| |Vertical | - Stack | Line 1 | | Line 2 | | Line 3 | | Line 4 | - | | | | | | | | - +--------+ +--------+ +--------+ +---------+ - crossDimensionSum - |------------------------------------------| - - @param lines unpositioned lines - */ -static CGFloat computeLinesCrossDimensionSum(const std::vector &lines, - const ASStackLayoutSpecStyle &style) -{ - return std::accumulate(lines.begin(), lines.end(), - // Start from default spacing between each line: - lines.empty() ? 0 : style.lineSpacing * (lines.size() - 1), - [&](CGFloat x, const ASStackUnpositionedLine &l) { - return x + l.crossSize; - }); -} - - -/** - Computes the violation by comparing a cross dimension sum with the overall allowable size range for the stack. - - Violation is the distance you would have to add to the unbounded cross-direction length of the stack spec's - lines in order to bring the stack within its allowed sizeRange. The diagram below shows 3 vertical stacks, each contains 3-5 vertical lines, - with the different types of violation. - - Cross Dimension - +---------------------> - cross size range - |------------| - +--------+ +--------+ +--------+ +---------+ - - - - - - - - - Vertical |Vertical| |Vertical| |Vertical| |Vertical | | ^ - Stack 1 | Line 1 | | Line 2 | | Line 3 | | Line 4 | (zero violation) | stack size range - | | | | | | | | | | v - +--------+ +--------+ +--------+ +---------+ - - - - - - - - - | | - +--------+ +--------+ +--------+ - - - - - - - - - - - - - Vertical | | | | | | | | ^ - Stack 2 | | | | | |<--> (positive violation) | stack size range - | | | | | | | | v - +--------+ +--------+ +--------+ - - - - - - - - - - - - - | |<------> (negative violation) - +--------+ +--------+ +--------+ +---------+ +-----------+ - - - - Vertical | | | | | | | | | | | | ^ - Stack 3 | | | | | | | | | | | stack size range - | | | | | | | | | | | | v - +--------+ +--------+ +--------+ +---------+ +-----------+ - - - - - @param crossDimensionSum the consumed length of the lines in the stack along the cross dimension - @param style layout style to be applied to all children - @param sizeRange the range of allowable sizes for the stack layout spec - */ -CGFloat ASStackUnpositionedLayout::computeCrossViolation(const CGFloat crossDimensionSum, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &sizeRange) -{ - const CGFloat minCrossDimension = crossDimension(style.direction, sizeRange.min); - const CGFloat maxCrossDimension = crossDimension(style.direction, sizeRange.max); - if (crossDimensionSum < minCrossDimension) { - return minCrossDimension - crossDimensionSum; - } else if (crossDimensionSum > maxCrossDimension) { - return maxCrossDimension - crossDimensionSum; - } - return 0; -} - -/** - Stretches children to lay out along the cross axis according to the alignment stretch settings of the children - (child.alignSelf), and the stack layout's alignment settings (style.alignItems). This does not do the actual alignment - of the items once stretched though; ASStackPositionedLayout will do centering etc. - - Finds the maximum cross dimension among child layouts. If that dimension exceeds the minimum cross layout size then - we must stretch any children whose alignItems specify ASStackLayoutAlignItemsStretch. - - The diagram below shows 3 children in a horizontal stack. The second child is larger than the minCrossDimension, so - its height is used as the childCrossMax. Any children that are stretchable (which may be all children if - style.alignItems specifies stretch) like the first child must be stretched to match that maximum. All children must be - at least minCrossDimension in cross dimension size, which is shown by the sizing of the third child. - - Stack Dimension - +---------------------> - + +-+-------------+-+-------------+--+---------------+ + + + - | | child. | | | | | | | | - | | alignSelf | | | | | | | | - Cross | | = stretch | | | +-------+-------+ | | | - Dimension | +-----+-------+ | | | | | | | | - | | | | | | | | | | - | | | | | v | | | | - v +-+- - - - - - -+-+ - - - - - - +--+- - - - - - - -+ | | + minCrossDimension - | | | | | - | v | | | | | - +- - - - - - -+ +-------------+ | + childCrossMax - | - +--------------------------------------------------+ + crossMax - - @param items pre-computed items; modified in-place as needed - @param style the layout style of the overall stack layout - */ -static void stretchItemsAlongCrossDimension(std::vector &items, - const ASStackLayoutSpecStyle &style, - const BOOL concurrent, - const CGSize parentSize, - const CGFloat crossSize) -{ - dispatchApplyIfNeeded(items.size(), concurrent, ^(size_t i) { - auto &item = items[i]; - const ASStackLayoutAlignItems alignItems = alignment(item.child.style.alignSelf, style.alignItems); - if (alignItems == ASStackLayoutAlignItemsStretch) { - const CGFloat cross = crossDimension(style.direction, item.layout.size); - const CGFloat stack = stackDimension(style.direction, item.layout.size); - const CGFloat violation = crossSize - cross; - - // Only stretch if violation is positive. Compare against kViolationEpsilon here to avoid stretching against a tiny violation. - if (violation > kViolationEpsilon) { - item.layout = crossChildLayout(item.child, style, stack, stack, crossSize, crossSize, parentSize); - } - } - }); -} - -/** - * Stretch lines and their items according to alignContent, alignItems and alignSelf. - * https://www.w3.org/TR/css-flexbox-1/#algo-line-stretch - * https://www.w3.org/TR/css-flexbox-1/#algo-stretch - */ -static void stretchLinesAlongCrossDimension(std::vector &lines, - const ASStackLayoutSpecStyle &style, - const BOOL concurrent, - const ASSizeRange &sizeRange, - const CGSize parentSize) -{ - ASDisplayNodeCAssertFalse(lines.empty()); - const std::size_t numOfLines = lines.size(); - const CGFloat violation = ASStackUnpositionedLayout::computeCrossViolation(computeLinesCrossDimensionSum(lines, style), style, sizeRange); - // Don't stretch if the stack is single line, because the line's cross size was clamped against the stack's constrained size. - const BOOL shouldStretchLines = (numOfLines > 1 - && style.alignContent == ASStackLayoutAlignContentStretch - && violation > kViolationEpsilon); - - CGFloat extraCrossSizePerLine = violation / numOfLines; - for (auto &line : lines) { - if (shouldStretchLines) { - line.crossSize += extraCrossSizePerLine; - } - - stretchItemsAlongCrossDimension(line.items, style, concurrent, parentSize, line.crossSize); - } -} - -static BOOL itemIsBaselineAligned(const ASStackLayoutSpecStyle &style, - const ASStackLayoutSpecItem &l) -{ - ASStackLayoutAlignItems alignItems = alignment(l.child.style.alignSelf, style.alignItems); - return alignItems == ASStackLayoutAlignItemsBaselineFirst || alignItems == ASStackLayoutAlignItemsBaselineLast; -} - -CGFloat ASStackUnpositionedLayout::baselineForItem(const ASStackLayoutSpecStyle &style, - const ASStackLayoutSpecItem &item) -{ - switch (alignment(item.child.style.alignSelf, style.alignItems)) { - case ASStackLayoutAlignItemsBaselineFirst: - return item.child.style.ascender; - case ASStackLayoutAlignItemsBaselineLast: - return crossDimension(style.direction, item.layout.size) + item.child.style.descender; - default: - return 0; - } -} - -/** - * Computes cross size and baseline of each line. - * https://www.w3.org/TR/css-flexbox-1/#algo-cross-line - * - * @param lines All items to lay out - * @param style the layout style of the overall stack layout - * @param sizeRange the range of allowable sizes for the stack layout component - */ -static void computeLinesCrossSizeAndBaseline(std::vector &lines, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &sizeRange) -{ - ASDisplayNodeCAssertFalse(lines.empty()); - const BOOL isSingleLine = (lines.size() == 1); - - const auto minCrossSize = crossDimension(style.direction, sizeRange.min); - const auto maxCrossSize = crossDimension(style.direction, sizeRange.max); - const BOOL definiteCrossSize = (minCrossSize == maxCrossSize); - - // If the stack is single-line and has a definite cross size, the cross size of the line is the stack's definite cross size. - if (isSingleLine && definiteCrossSize) { - auto &line = lines[0]; - line.crossSize = minCrossSize; - - // We still need to determine the line's baseline - //TODO unit test - for (const auto &item : line.items) { - if (itemIsBaselineAligned(style, item)) { - CGFloat baseline = ASStackUnpositionedLayout::baselineForItem(style, item); - line.baseline = MAX(line.baseline, baseline); - } - } - - return; - } - - for (auto &line : lines) { - const auto &items = line.items; - CGFloat maxStartToBaselineDistance = 0; - CGFloat maxBaselineToEndDistance = 0; - CGFloat maxItemCrossSize = 0; - - for (const auto &item : items) { - if (itemIsBaselineAligned(style, item)) { - // Step 1. Collect all the items whose align-self is baseline. Find the largest of the distances - // between each item’s baseline and its hypothetical outer cross-start edge (aka. its baseline value), - // and the largest of the distances between each item’s baseline and its hypothetical outer cross-end edge, - // and sum these two values. - CGFloat baseline = ASStackUnpositionedLayout::baselineForItem(style, item); - maxStartToBaselineDistance = MAX(maxStartToBaselineDistance, baseline); - maxBaselineToEndDistance = MAX(maxBaselineToEndDistance, crossDimension(style.direction, item.layout.size) - baseline); - } else { - // Step 2. Among all the items not collected by the previous step, find the largest outer hypothetical cross size. - maxItemCrossSize = MAX(maxItemCrossSize, crossDimension(style.direction, item.layout.size)); - } - } - - // Step 3. The used cross-size of the flex line is the largest of the numbers found in the previous two steps and zero. - line.crossSize = MAX(maxStartToBaselineDistance + maxBaselineToEndDistance, maxItemCrossSize); - if (isSingleLine) { - // If the stack is single-line, then clamp the line’s cross-size to be within the stack's min and max cross-size properties. - line.crossSize = MIN(MAX(minCrossSize, line.crossSize), maxCrossSize); - } - - line.baseline = maxStartToBaselineDistance; - } -} - -/** - Returns a lambda that computes the relevant flex factor based on the given violation. - @param violation The amount that the stack layout violates its size range. See header for sign interpretation. - */ -static std::function flexFactorInViolationDirection(const CGFloat violation) -{ - if (std::fabs(violation) < kViolationEpsilon) { - return [](const ASStackLayoutSpecItem &item) { return 0.0; }; - } else if (violation > 0) { - return [](const ASStackLayoutSpecItem &item) { return item.child.style.flexGrow; }; - } else { - return [](const ASStackLayoutSpecItem &item) { return item.child.style.flexShrink; }; - } -} - -static inline CGFloat scaledFlexShrinkFactor(const ASStackLayoutSpecItem &item, - const ASStackLayoutSpecStyle &style, - const CGFloat flexFactorSum) -{ - return stackDimension(style.direction, item.layout.size) * (item.child.style.flexShrink / flexFactorSum); -} - -/** - Returns a lambda that computes a flex shrink adjustment for a given item based on the provided violation. - @param items The unpositioned items from the original unconstrained layout pass. - @param style The layout style to be applied to all children. - @param violation The amount that the stack layout violates its size range. - @param flexFactorSum The sum of each item's flex factor as determined by the provided violation. - @return A lambda capable of computing the flex shrink adjustment, if any, for a particular item. - */ -static std::function flexShrinkAdjustment(const std::vector &items, - const ASStackLayoutSpecStyle &style, - const CGFloat violation, - const CGFloat flexFactorSum) -{ - const CGFloat scaledFlexShrinkFactorSum = std::accumulate(items.begin(), items.end(), 0.0, [&](CGFloat x, const ASStackLayoutSpecItem &item) { - return x + scaledFlexShrinkFactor(item, style, flexFactorSum); - }); - return [style, scaledFlexShrinkFactorSum, violation, flexFactorSum](const ASStackLayoutSpecItem &item) { - if (scaledFlexShrinkFactorSum == 0.0) { - return (CGFloat)0.0; - } - - const CGFloat scaledFlexShrinkFactorRatio = scaledFlexShrinkFactor(item, style, flexFactorSum) / scaledFlexShrinkFactorSum; - // The item should shrink proportionally to the scaled flex shrink factor ratio computed above. - // Unlike the flex grow adjustment the flex shrink adjustment needs to take the size of each item into account. - return -std::fabs(scaledFlexShrinkFactorRatio * violation); - }; -} - -/** - Returns a lambda that computes a flex grow adjustment for a given item based on the provided violation. - @param items The unpositioned items from the original unconstrained layout pass. - @param violation The amount that the stack layout violates its size range. - @param flexFactorSum The sum of each item's flex factor as determined by the provided violation. - @return A lambda capable of computing the flex grow adjustment, if any, for a particular item. - */ -static std::function flexGrowAdjustment(const std::vector &items, - const CGFloat violation, - const CGFloat flexFactorSum) -{ - // To compute the flex grow adjustment distribute the violation proportionally based on each item's flex grow factor. - return [violation, flexFactorSum](const ASStackLayoutSpecItem &item) { - return std::floor(violation * (item.child.style.flexGrow / flexFactorSum)); - }; -} - -/** - Returns a lambda that computes a flex adjustment for a given item based on the provided violation. - @param items The unpositioned items from the original unconstrained layout pass. - @param style The layout style to be applied to all children. - @param violation The amount that the stack layout violates its size range. - @param flexFactorSum The sum of each item's flex factor as determined by the provided violation. - @return A lambda capable of computing the flex adjustment for a particular item. - */ -static std::function flexAdjustmentInViolationDirection(const std::vector &items, - const ASStackLayoutSpecStyle &style, - const CGFloat violation, - const CGFloat flexFactorSum) -{ - if (violation > 0) { - return flexGrowAdjustment(items, violation, flexFactorSum); - } else { - return flexShrinkAdjustment(items, style, violation, flexFactorSum); - } -} - -ASDISPLAYNODE_INLINE BOOL isFlexibleInBothDirections(const ASStackLayoutSpecChild &child) -{ - return child.style.flexGrow > 0 && child.style.flexShrink > 0; -} - -/** - The flexible children may have been left not laid out in the initial layout pass, so we may have to go through and size - these children at zero size so that the children layouts are at least present. - */ -static void layoutFlexibleChildrenAtZeroSize(std::vector &items, - const ASStackLayoutSpecStyle &style, - const BOOL concurrent, - const ASSizeRange &sizeRange, - const CGSize parentSize) -{ - dispatchApplyIfNeeded(items.size(), concurrent, ^(size_t i) { - auto &item = items[i]; - if (isFlexibleInBothDirections(item.child)) { - item.layout = crossChildLayout(item.child, - style, - 0, - 0, - crossDimension(style.direction, sizeRange.min), - crossDimension(style.direction, sizeRange.max), - parentSize); - } - }); -} - -/** - Computes the consumed stack dimension length for the given vector of items and stacking style. - - stackDimensionSum - <-----------------------> - +-----+ +-------+ +---+ - | | | | | | - | | | | | | - +-----+ | | +---+ - +-------+ - - @param items unpositioned layouts for items - @param style the layout style of the overall stack layout - */ -static CGFloat computeItemsStackDimensionSum(const std::vector &items, - const ASStackLayoutSpecStyle &style) -{ - // Sum up the childrens' spacing - const CGFloat childSpacingSum = std::accumulate(items.begin(), items.end(), - // Start from default spacing between each child: - items.empty() ? 0 : style.spacing * (items.size() - 1), - [&](CGFloat x, const ASStackLayoutSpecItem &l) { - return x + l.child.style.spacingBefore + l.child.style.spacingAfter; - }); - - // Sum up the childrens' dimensions (including spacing) in the stack direction. - const CGFloat childStackDimensionSum = std::accumulate(items.begin(), items.end(), - childSpacingSum, - [&](CGFloat x, const ASStackLayoutSpecItem &l) { - return x + stackDimension(style.direction, l.layout.size); - }); - return childStackDimensionSum; -} - -//TODO move this up near computeCrossViolation and make both methods share the same code path, to make sure they share the same concept of "negative" and "positive" violations. -/** - Computes the violation by comparing a stack dimension sum with the overall allowable size range for the stack. - - Violation is the distance you would have to add to the unbounded stack-direction length of the stack spec's - children in order to bring the stack within its allowed sizeRange. The diagram below shows 3 horizontal stacks with - the different types of violation. - - sizeRange - |------------| - +------+ +-------+ +-------+ +---------+ - | | | | | | | | | | - | | | | | | | | (zero violation) - | | | | | | | | | | - +------+ +-------+ +-------+ +---------+ - | | - +------+ +-------+ +-------+ - | | | | | | | | - | | | | | |<--> (positive violation) - | | | | | | | | - +------+ +-------+ +-------+ - | |<------> (negative violation) - +------+ +-------+ +-------+ +---------+ +-----------+ - | | | | | | | | | | | | - | | | | | | | | | | - | | | | | | | | | | | | - +------+ +-------+ +-------+ +---------+ +-----------+ - - @param stackDimensionSum the consumed length of the children in the stack along the stack dimension - @param style layout style to be applied to all children - @param sizeRange the range of allowable sizes for the stack layout spec - */ -CGFloat ASStackUnpositionedLayout::computeStackViolation(const CGFloat stackDimensionSum, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &sizeRange) -{ - const CGFloat minStackDimension = stackDimension(style.direction, sizeRange.min); - const CGFloat maxStackDimension = stackDimension(style.direction, sizeRange.max); - if (stackDimensionSum < minStackDimension) { - return minStackDimension - stackDimensionSum; - } else if (stackDimensionSum > maxStackDimension) { - return maxStackDimension - stackDimensionSum; - } - return 0; -} - -/** - If we have a single flexible (both shrinkable and growable) child, and our allowed size range is set to a specific - number then we may avoid the first "intrinsic" size calculation. - */ -ASDISPLAYNODE_INLINE BOOL useOptimizedFlexing(const std::vector &children, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &sizeRange) -{ - const NSUInteger flexibleChildren = std::count_if(children.begin(), children.end(), isFlexibleInBothDirections); - return ((flexibleChildren == 1) - && (stackDimension(style.direction, sizeRange.min) == - stackDimension(style.direction, sizeRange.max))); -} - -/** - Flexes children in the stack axis to resolve a min or max stack size violation. First, determines which children are - flexible (see computeStackViolation and isFlexibleInViolationDirection). Then computes how much to flex each flexible child - and performs re-layout. Note that there may still be a non-zero violation even after flexing. - - The actual CSS flexbox spec describes an iterative looping algorithm here, which may be adopted in t5837937: - http://www.w3.org/TR/css3-flexbox/#resolve-flexible-lengths - - @param lines reference to unpositioned lines and items from the original, unconstrained layout pass; modified in-place - @param style layout style to be applied to all children - @param sizeRange the range of allowable sizes for the stack layout component - @param parentSize Size of the stack layout component. May be undefined in either or both directions. - */ -static void flexLinesAlongStackDimension(std::vector &lines, - const ASStackLayoutSpecStyle &style, - const BOOL concurrent, - const ASSizeRange &sizeRange, - const CGSize parentSize, - const BOOL useOptimizedFlexing) -{ - for (auto &line : lines) { - auto &items = line.items; - const CGFloat violation = ASStackUnpositionedLayout::computeStackViolation(computeItemsStackDimensionSum(items, style), style, sizeRange); - std::function flexFactor = flexFactorInViolationDirection(violation); - // The flex factor sum is needed to determine if flexing is necessary. - // This value is also needed if the violation is positive and flexible items need to grow, so keep it around. - const CGFloat flexFactorSum = std::accumulate(items.begin(), items.end(), 0.0, [&](CGFloat x, const ASStackLayoutSpecItem &item) { - return x + flexFactor(item); - }); - - // If no items are able to flex then there is nothing left to do with this line. Bail. - if (flexFactorSum == 0) { - // If optimized flexing was used then we have to clean up the unsized items and lay them out at zero size. - if (useOptimizedFlexing) { - layoutFlexibleChildrenAtZeroSize(items, style, concurrent, sizeRange, parentSize); - } - continue; - } - - std::function flexAdjustment = flexAdjustmentInViolationDirection(items, - style, - violation, - flexFactorSum); - // Compute any remaining violation to the first flexible item. - const CGFloat remainingViolation = std::accumulate(items.begin(), items.end(), violation, [&](CGFloat x, const ASStackLayoutSpecItem &item) { - return x - flexAdjustment(item); - }); - - size_t firstFlexItem = -1; - for(size_t i = 0; i < items.size(); i++) { - // Items are consider inflexible if they do not need to make a flex adjustment. - if (flexAdjustment(items[i]) != 0) { - firstFlexItem = i; - break; - } - } - if (firstFlexItem == -1) { - continue; - } - - dispatchApplyIfNeeded(items.size(), concurrent, ^(size_t i) { - auto &item = items[i]; - const CGFloat currentFlexAdjustment = flexAdjustment(item); - // Items are consider inflexible if they do not need to make a flex adjustment. - if (currentFlexAdjustment != 0) { - const CGFloat originalStackSize = stackDimension(style.direction, item.layout.size); - // Only apply the remaining violation for the first flexible item that has a flex grow factor. - const CGFloat flexedStackSize = originalStackSize + currentFlexAdjustment + (i == firstFlexItem && item.child.style.flexGrow > 0 ? remainingViolation : 0); - item.layout = crossChildLayout(item.child, - style, - MAX(flexedStackSize, 0), - MAX(flexedStackSize, 0), - crossDimension(style.direction, sizeRange.min), - crossDimension(style.direction, sizeRange.max), - parentSize); - } - }); - } -} - -/** - https://www.w3.org/TR/css-flexbox-1/#algo-line-break - */ -static std::vector collectChildrenIntoLines(const std::vector &items, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &sizeRange) -{ - //TODO if infinite max stack size, fast path - if (style.flexWrap == ASStackLayoutFlexWrapNoWrap) { - return std::vector (1, {.items = std::move(items)}); - } - - std::vector lines; - std::vector lineItems; - CGFloat lineStackDimensionSum = 0; - CGFloat interitemSpacing = 0; - - for(auto it = items.begin(); it != items.end(); ++it) { - const auto &item = *it; - const CGFloat itemStackDimension = stackDimension(style.direction, item.layout.size); - const CGFloat itemAndSpacingStackDimension = item.child.style.spacingBefore + itemStackDimension + item.child.style.spacingAfter; - const BOOL negativeViolationIfAddItem = (ASStackUnpositionedLayout::computeStackViolation(lineStackDimensionSum + interitemSpacing + itemAndSpacingStackDimension, style, sizeRange) < 0); - const BOOL breakCurrentLine = negativeViolationIfAddItem && !lineItems.empty(); - - if (breakCurrentLine) { - lines.push_back({.items = std::vector (lineItems)}); - lineItems.clear(); - lineStackDimensionSum = 0; - interitemSpacing = 0; - } - - lineItems.push_back(std::move(item)); - lineStackDimensionSum += interitemSpacing + itemAndSpacingStackDimension; - interitemSpacing = style.spacing; - } - - // Handle last line - lines.push_back({.items = std::vector (lineItems)}); - - return lines; -} - -/** - Performs the first unconstrained layout of the children, generating the unpositioned items that are then flexed and - stretched. - */ -static void layoutItemsAlongUnconstrainedStackDimension(std::vector &items, - const ASStackLayoutSpecStyle &style, - const BOOL concurrent, - const ASSizeRange &sizeRange, - const CGSize parentSize, - const BOOL useOptimizedFlexing) -{ - const CGFloat minCrossDimension = crossDimension(style.direction, sizeRange.min); - const CGFloat maxCrossDimension = crossDimension(style.direction, sizeRange.max); - - dispatchApplyIfNeeded(items.size(), concurrent, ^(size_t i) { - auto &item = items[i]; - if (useOptimizedFlexing && isFlexibleInBothDirections(item.child)) { - item.layout = [ASLayout layoutWithLayoutElement:item.child.element size:{0, 0}]; - } else { - item.layout = crossChildLayout(item.child, - style, - ASDimensionResolve(item.child.style.flexBasis, stackDimension(style.direction, parentSize), 0), - ASDimensionResolve(item.child.style.flexBasis, stackDimension(style.direction, parentSize), INFINITY), - minCrossDimension, - maxCrossDimension, - parentSize); - } - }); -} - -ASStackUnpositionedLayout ASStackUnpositionedLayout::compute(const std::vector &children, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &sizeRange, - const BOOL concurrent) -{ - if (children.empty()) { - return {}; - } - - // If we have a fixed size in either dimension, pass it to children so they can resolve percentages against it. - // Otherwise, we pass ASLayoutElementParentDimensionUndefined since it will depend on the content. - const CGSize parentSize = { - (sizeRange.min.width == sizeRange.max.width) ? sizeRange.min.width : ASLayoutElementParentDimensionUndefined, - (sizeRange.min.height == sizeRange.max.height) ? sizeRange.min.height : ASLayoutElementParentDimensionUndefined, - }; - - // We may be able to avoid some redundant layout passes - const BOOL optimizedFlexing = useOptimizedFlexing(children, style, sizeRange); - - std::vector items = AS::map(children, [&](const ASStackLayoutSpecChild &child) -> ASStackLayoutSpecItem { - return {child, nil}; - }); - - // We do a first pass of all the children, generating an unpositioned layout for each with an unbounded range along - // the stack dimension. This allows us to compute the "intrinsic" size of each child and find the available violation - // which determines whether we must grow or shrink the flexible children. - layoutItemsAlongUnconstrainedStackDimension(items, - style, - concurrent, - sizeRange, - parentSize, - optimizedFlexing); - - // Collect items into lines (https://www.w3.org/TR/css-flexbox-1/#algo-line-break) - std::vector lines = collectChildrenIntoLines(items, style, sizeRange); - - // Resolve the flexible lengths (https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths) - flexLinesAlongStackDimension(lines, style, concurrent, sizeRange, parentSize, optimizedFlexing); - - // Calculate the cross size of each flex line (https://www.w3.org/TR/css-flexbox-1/#algo-cross-line) - computeLinesCrossSizeAndBaseline(lines, style, sizeRange); - - // Handle 'align-content: stretch' (https://www.w3.org/TR/css-flexbox-1/#algo-line-stretch) - // Determine the used cross size of each item (https://www.w3.org/TR/css-flexbox-1/#algo-stretch) - stretchLinesAlongCrossDimension(lines, style, concurrent, sizeRange, parentSize); - - // Compute stack dimension sum of each line and the whole stack - CGFloat layoutStackDimensionSum = 0; - for (auto &line : lines) { - line.stackDimensionSum = computeItemsStackDimensionSum(line.items, style); - // layoutStackDimensionSum is the max stackDimensionSum among all lines - layoutStackDimensionSum = MAX(line.stackDimensionSum, layoutStackDimensionSum); - } - // Compute cross dimension sum of the stack. - // This should be done before `lines` are moved to a new ASStackUnpositionedLayout struct (i.e `std::move(lines)`) - CGFloat layoutCrossDimensionSum = computeLinesCrossDimensionSum(lines, style); - - return {.lines = std::move(lines), .stackDimensionSum = layoutStackDimensionSum, .crossDimensionSum = layoutCrossDimensionSum}; -} diff --git a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Component/ASTextDebugOption.h b/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Component/ASTextDebugOption.h deleted file mode 100644 index efa7808c87..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Component/ASTextDebugOption.h +++ /dev/null @@ -1,92 +0,0 @@ -// -// ASTextDebugOption.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@class ASTextDebugOption; - -NS_ASSUME_NONNULL_BEGIN - -/** - The ASTextDebugTarget protocol defines the method a debug target should implement. - A debug target can be add to the global container to receive the shared debug - option changed notification. - */ -@protocol ASTextDebugTarget - -@required -/** - When the shared debug option changed, this method would be called on main thread. - It should return as quickly as possible. The option's property should not be changed - in this method. - - @param option The shared debug option. - */ -- (void)setDebugOption:(nullable ASTextDebugOption *)option; -@end - - - -/** - The debug option for ASText. - */ -@interface ASTextDebugOption : NSObject -@property (nullable, nonatomic) UIColor *baselineColor; ///< baseline color -@property (nullable, nonatomic) UIColor *CTFrameBorderColor; ///< CTFrame path border color -@property (nullable, nonatomic) UIColor *CTFrameFillColor; ///< CTFrame path fill color -@property (nullable, nonatomic) UIColor *CTLineBorderColor; ///< CTLine bounds border color -@property (nullable, nonatomic) UIColor *CTLineFillColor; ///< CTLine bounds fill color -@property (nullable, nonatomic) UIColor *CTLineNumberColor; ///< CTLine line number color -@property (nullable, nonatomic) UIColor *CTRunBorderColor; ///< CTRun bounds border color -@property (nullable, nonatomic) UIColor *CTRunFillColor; ///< CTRun bounds fill color -@property (nullable, nonatomic) UIColor *CTRunNumberColor; ///< CTRun number color -@property (nullable, nonatomic) UIColor *CGGlyphBorderColor; ///< CGGlyph bounds border color -@property (nullable, nonatomic) UIColor *CGGlyphFillColor; ///< CGGlyph bounds fill color - -- (BOOL)needDrawDebug; ///< `YES`: at least one debug color is visible. `NO`: all debug color is invisible/nil. -- (void)clear; ///< Set all debug color to nil. - -/** - Add a debug target. - - @discussion When `setSharedDebugOption:` is called, all added debug target will - receive `setDebugOption:` in main thread. It maintains an unsafe_unretained - reference to this target. The target must to removed before dealloc. - - @param target A debug target. - */ -+ (void)addDebugTarget:(id)target; - -/** - Remove a debug target which is added by `addDebugTarget:`. - - @param target A debug target. - */ -+ (void)removeDebugTarget:(id)target; - -/** - Returns the shared debug option. - - @return The shared debug option, default is nil. - */ -+ (nullable ASTextDebugOption *)sharedDebugOption; - -/** - Set a debug option as shared debug option. - This method must be called on main thread. - - @discussion When call this method, the new option will set to all debug target - which is added by `addDebugTarget:`. - - @param option A new debug option (nil is valid). - */ -+ (void)setSharedDebugOption:(nullable ASTextDebugOption *)option; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Component/ASTextDebugOption.mm b/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Component/ASTextDebugOption.mm deleted file mode 100644 index 2565b903c9..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Component/ASTextDebugOption.mm +++ /dev/null @@ -1,135 +0,0 @@ -// -// ASTextDebugOption.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASTextDebugOption.h" -#import - -static pthread_mutex_t _sharedDebugLock; -static CFMutableSetRef _sharedDebugTargets = nil; -static ASTextDebugOption *_sharedDebugOption = nil; - -static const void* _as_sharedDebugSetRetain(CFAllocatorRef allocator, const void *value) { - return value; -} - -static void _as_sharedDebugSetRelease(CFAllocatorRef allocator, const void *value) { -} - -void _as_sharedDebugSetFunction(const void *value, void *context) { - id target = (__bridge id)(value); - [target setDebugOption:_sharedDebugOption]; -} - -static void _initSharedDebug() { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - pthread_mutex_init(&_sharedDebugLock, NULL); - CFSetCallBacks callbacks = kCFTypeSetCallBacks; - callbacks.retain = _as_sharedDebugSetRetain; - callbacks.release = _as_sharedDebugSetRelease; - _sharedDebugTargets = CFSetCreateMutable(CFAllocatorGetDefault(), 0, &callbacks); - }); -} - -static void _setSharedDebugOption(ASTextDebugOption *option) { - _initSharedDebug(); - pthread_mutex_lock(&_sharedDebugLock); - _sharedDebugOption = option.copy; - CFSetApplyFunction(_sharedDebugTargets, _as_sharedDebugSetFunction, NULL); - pthread_mutex_unlock(&_sharedDebugLock); -} - -static ASTextDebugOption *_getSharedDebugOption() { - _initSharedDebug(); - pthread_mutex_lock(&_sharedDebugLock); - ASTextDebugOption *op = _sharedDebugOption; - pthread_mutex_unlock(&_sharedDebugLock); - return op; -} - -static void _addDebugTarget(id target) { - _initSharedDebug(); - pthread_mutex_lock(&_sharedDebugLock); - CFSetAddValue(_sharedDebugTargets, (__bridge const void *)(target)); - pthread_mutex_unlock(&_sharedDebugLock); -} - -static void _removeDebugTarget(id target) { - _initSharedDebug(); - pthread_mutex_lock(&_sharedDebugLock); - CFSetRemoveValue(_sharedDebugTargets, (__bridge const void *)(target)); - pthread_mutex_unlock(&_sharedDebugLock); -} - - -@implementation ASTextDebugOption - -- (id)copyWithZone:(NSZone *)zone { - ASTextDebugOption *op = [self.class new]; - op.baselineColor = self.baselineColor; - op.CTFrameBorderColor = self.CTFrameBorderColor; - op.CTFrameFillColor = self.CTFrameFillColor; - op.CTLineBorderColor = self.CTLineBorderColor; - op.CTLineFillColor = self.CTLineFillColor; - op.CTLineNumberColor = self.CTLineNumberColor; - op.CTRunBorderColor = self.CTRunBorderColor; - op.CTRunFillColor = self.CTRunFillColor; - op.CTRunNumberColor = self.CTRunNumberColor; - op.CGGlyphBorderColor = self.CGGlyphBorderColor; - op.CGGlyphFillColor = self.CGGlyphFillColor; - return op; -} - -- (BOOL)needDrawDebug { - if (self.baselineColor || - self.CTFrameBorderColor || - self.CTFrameFillColor || - self.CTLineBorderColor || - self.CTLineFillColor || - self.CTLineNumberColor || - self.CTRunBorderColor || - self.CTRunFillColor || - self.CTRunNumberColor || - self.CGGlyphBorderColor || - self.CGGlyphFillColor) return YES; - return NO; -} - -- (void)clear { - self.baselineColor = nil; - self.CTFrameBorderColor = nil; - self.CTFrameFillColor = nil; - self.CTLineBorderColor = nil; - self.CTLineFillColor = nil; - self.CTLineNumberColor = nil; - self.CTRunBorderColor = nil; - self.CTRunFillColor = nil; - self.CTRunNumberColor = nil; - self.CGGlyphBorderColor = nil; - self.CGGlyphFillColor = nil; -} - -+ (void)addDebugTarget:(id)target { - if (target) _addDebugTarget(target); -} - -+ (void)removeDebugTarget:(id)target { - if (target) _removeDebugTarget(target); -} - -+ (ASTextDebugOption *)sharedDebugOption { - return _getSharedDebugOption(); -} - -+ (void)setSharedDebugOption:(ASTextDebugOption *)option { - NSAssert([NSThread isMainThread], @"This method must be called on the main thread"); - _setSharedDebugOption(option); -} - -@end - diff --git a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Component/ASTextInput.h b/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Component/ASTextInput.h deleted file mode 100644 index 9a6cbd13d1..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Component/ASTextInput.h +++ /dev/null @@ -1,85 +0,0 @@ -// -// ASTextInput.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - Text position affinity. For example, the offset appears after the last - character on a line is backward affinity, before the first character on - the following line is forward affinity. - */ -typedef NS_ENUM(NSInteger, ASTextAffinity) { - ASTextAffinityForward = 0, ///< offset appears before the character - ASTextAffinityBackward = 1, ///< offset appears after the character -}; - - -/** - A ASTextPosition object represents a position in a text container; in other words, - it is an index into the backing string in a text-displaying view. - - ASTextPosition has the same API as Apple's implementation in UITextView/UITextField, - so you can alse use it to interact with UITextView/UITextField. - */ -@interface ASTextPosition : UITextPosition - -@property (nonatomic, readonly) NSInteger offset; -@property (nonatomic, readonly) ASTextAffinity affinity; - -+ (instancetype)positionWithOffset:(NSInteger)offset NS_RETURNS_RETAINED; -+ (instancetype)positionWithOffset:(NSInteger)offset affinity:(ASTextAffinity) affinity NS_RETURNS_RETAINED; - -- (NSComparisonResult)compare:(id)otherPosition; - -@end - - -/** - A ASTextRange object represents a range of characters in a text container; in other words, - it identifies a starting index and an ending index in string backing a text-displaying view. - - ASTextRange has the same API as Apple's implementation in UITextView/UITextField, - so you can alse use it to interact with UITextView/UITextField. - */ -@interface ASTextRange : UITextRange - -@property (nonatomic, readonly) ASTextPosition *start; -@property (nonatomic, readonly) ASTextPosition *end; -@property (nonatomic, readonly, getter=isEmpty) BOOL empty; - -+ (instancetype)rangeWithRange:(NSRange)range NS_RETURNS_RETAINED; -+ (instancetype)rangeWithRange:(NSRange)range affinity:(ASTextAffinity) affinity NS_RETURNS_RETAINED; -+ (instancetype)rangeWithStart:(ASTextPosition *)start end:(ASTextPosition *)end NS_RETURNS_RETAINED; -+ (instancetype)defaultRange NS_RETURNS_RETAINED; ///< <{0,0} Forward> - -- (NSRange)asRange; - -@end - - -/** - A ASTextSelectionRect object encapsulates information about a selected range of - text in a text-displaying view. - - ASTextSelectionRect has the same API as Apple's implementation in UITextView/UITextField, - so you can alse use it to interact with UITextView/UITextField. - */ -@interface ASTextSelectionRect : UITextSelectionRect - -@property (nonatomic) CGRect rect; -@property (nonatomic) UITextWritingDirection writingDirection; -@property (nonatomic) BOOL containsStart; -@property (nonatomic) BOOL containsEnd; -@property (nonatomic) BOOL isVertical; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Component/ASTextInput.mm b/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Component/ASTextInput.mm deleted file mode 100644 index 1cdfe73858..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Component/ASTextInput.mm +++ /dev/null @@ -1,150 +0,0 @@ -// -// ASTextInput.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - - -@implementation ASTextPosition - -+ (instancetype)positionWithOffset:(NSInteger)offset NS_RETURNS_RETAINED { - return [self positionWithOffset:offset affinity:ASTextAffinityForward]; -} - -+ (instancetype)positionWithOffset:(NSInteger)offset affinity:(ASTextAffinity)affinity NS_RETURNS_RETAINED { - ASTextPosition *p = [self new]; - p->_offset = offset; - p->_affinity = affinity; - return p; -} - -- (instancetype)copyWithZone:(NSZone *)zone { - return [self.class positionWithOffset:_offset affinity:_affinity]; -} - -- (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p> (%@%@)", self.class, self, @(_offset), _affinity == ASTextAffinityForward ? @"F":@"B"]; -} - -- (NSUInteger)hash { - return _offset * 2 + (_affinity == ASTextAffinityForward ? 1 : 0); -} - -- (BOOL)isEqual:(ASTextPosition *)object { - if (!object) return NO; - return _offset == object.offset && _affinity == object.affinity; -} - -- (NSComparisonResult)compare:(ASTextPosition *)otherPosition { - if (!otherPosition) return NSOrderedAscending; - if (_offset < otherPosition.offset) return NSOrderedAscending; - if (_offset > otherPosition.offset) return NSOrderedDescending; - if (_affinity == ASTextAffinityBackward && otherPosition.affinity == ASTextAffinityForward) return NSOrderedAscending; - if (_affinity == ASTextAffinityForward && otherPosition.affinity == ASTextAffinityBackward) return NSOrderedDescending; - return NSOrderedSame; -} - -@end - - - -@implementation ASTextRange { - ASTextPosition *_start; - ASTextPosition *_end; -} - -- (instancetype)init { - self = [super init]; - if (!self) return nil; - _start = [ASTextPosition positionWithOffset:0]; - _end = [ASTextPosition positionWithOffset:0]; - return self; -} - -- (ASTextPosition *)start { - return _start; -} - -- (ASTextPosition *)end { - return _end; -} - -- (BOOL)isEmpty { - return _start.offset == _end.offset; -} - -- (NSRange)asRange { - return NSMakeRange(_start.offset, _end.offset - _start.offset); -} - -+ (instancetype)rangeWithRange:(NSRange)range NS_RETURNS_RETAINED { - return [self rangeWithRange:range affinity:ASTextAffinityForward]; -} - -+ (instancetype)rangeWithRange:(NSRange)range affinity:(ASTextAffinity)affinity NS_RETURNS_RETAINED { - ASTextPosition *start = [ASTextPosition positionWithOffset:range.location affinity:affinity]; - ASTextPosition *end = [ASTextPosition positionWithOffset:range.location + range.length affinity:affinity]; - return [self rangeWithStart:start end:end]; -} - -+ (instancetype)rangeWithStart:(ASTextPosition *)start end:(ASTextPosition *)end NS_RETURNS_RETAINED { - if (!start || !end) return nil; - if ([start compare:end] == NSOrderedDescending) { - ASTEXT_SWAP(start, end); - } - ASTextRange *range = [ASTextRange new]; - range->_start = start; - range->_end = end; - return range; -} - -+ (instancetype)defaultRange NS_RETURNS_RETAINED { - return [self new]; -} - -- (instancetype)copyWithZone:(NSZone *)zone { - return [self.class rangeWithStart:_start end:_end]; -} - -- (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p> (%@, %@)%@", self.class, self, @(_start.offset), @(_end.offset - _start.offset), _end.affinity == ASTextAffinityForward ? @"F":@"B"]; -} - -- (NSUInteger)hash { - return (sizeof(NSUInteger) == 8 ? OSSwapInt64(_start.hash) : OSSwapInt32(_start.hash)) + _end.hash; -} - -- (BOOL)isEqual:(ASTextRange *)object { - if (!object) return NO; - return [_start isEqual:object.start] && [_end isEqual:object.end]; -} - -@end - - - -@implementation ASTextSelectionRect - -@synthesize rect = _rect; -@synthesize writingDirection = _writingDirection; -@synthesize containsStart = _containsStart; -@synthesize containsEnd = _containsEnd; -@synthesize isVertical = _isVertical; - -- (id)copyWithZone:(NSZone *)zone { - ASTextSelectionRect *one = [self.class new]; - one.rect = _rect; - one.writingDirection = _writingDirection; - one.containsStart = _containsStart; - one.containsEnd = _containsEnd; - one.isVertical = _isVertical; - return one; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Component/ASTextLayout.h b/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Component/ASTextLayout.h deleted file mode 100644 index 46bc8ccf52..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Component/ASTextLayout.h +++ /dev/null @@ -1,547 +0,0 @@ -// -// ASTextLayout.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -#import "ASTextDebugOption.h" -#import "ASTextLine.h" -#import "ASTextInput.h" - -@protocol ASTextLinePositionModifier; - -NS_ASSUME_NONNULL_BEGIN - -/** - The max text container size in layout. - */ -AS_EXTERN const CGSize ASTextContainerMaxSize; - -/** - The ASTextContainer class defines a region in which text is laid out. - ASTextLayout class uses one or more ASTextContainer objects to generate layouts. - - A ASTextContainer defines rectangular regions (`size` and `insets`) or - nonrectangular shapes (`path`), and you can define exclusion paths inside the - text container's bounding rectangle so that text flows around the exclusion - path as it is laid out. - - All methods in this class is thread-safe. - - Example: - - ┌─────────────────────────────┐ <------- container - │ │ - │ asdfasdfasdfasdfasdfa <------------ container insets - │ asdfasdfa asdfasdfa │ - │ asdfas asdasd │ - │ asdfa <----------------------- container exclusion path - │ asdfas adfasd │ - │ asdfasdfa asdfasdfa │ - │ asdfasdfasdfasdfasdfa │ - │ │ - └─────────────────────────────┘ - */ -@interface ASTextContainer : NSObject - -/// Creates a container with the specified size. @param size The size. -+ (instancetype)containerWithSize:(CGSize)size NS_RETURNS_RETAINED; - -/// Creates a container with the specified size and insets. @param size The size. @param insets The text insets. -+ (instancetype)containerWithSize:(CGSize)size insets:(UIEdgeInsets)insets NS_RETURNS_RETAINED; - -/// Creates a container with the specified path. @param path The path. -+ (instancetype)containerWithPath:(nullable UIBezierPath *)path NS_RETURNS_RETAINED; - -/// Mark this immutable, so you get free copies going forward. -- (void)makeImmutable; - -/// The constrained size. (if the size is larger than ASTextContainerMaxSize, it will be clipped) -@property CGSize size; - -/// The insets for constrained size. The inset value should not be negative. Default is UIEdgeInsetsZero. -@property UIEdgeInsets insets; - -/// Custom constrained path. Set this property to ignore `size` and `insets`. Default is nil. -@property (nullable, copy) UIBezierPath *path; - -/// An array of `UIBezierPath` for path exclusion. Default is nil. -@property (nullable, copy) NSArray *exclusionPaths; - -/// Path line width. Default is 0; -@property CGFloat pathLineWidth; - -/// YES:(PathFillEvenOdd) Text is filled in the area that would be painted if the path were given to CGContextEOFillPath. -/// NO: (PathFillWindingNumber) Text is fill in the area that would be painted if the path were given to CGContextFillPath. -/// Default is YES; -@property (getter=isPathFillEvenOdd) BOOL pathFillEvenOdd; - -/// Whether the text is vertical form (may used for CJK text layout). Default is NO. -@property (getter=isVerticalForm) BOOL verticalForm; - -/// Maximum number of rows, 0 means no limit. Default is 0. -@property NSUInteger maximumNumberOfRows; - -/// The line truncation type, default is none. -@property ASTextTruncationType truncationType; - -/// The truncation token. If nil, the layout will use "…" instead. Default is nil. -@property (nullable, copy) NSAttributedString *truncationToken; - -/// This modifier is applied to the lines before the layout is completed, -/// give you a chance to modify the line position. Default is nil. -@property (nullable, copy) id linePositionModifier; -@end - - -/** - The ASTextLinePositionModifier protocol declares the required method to modify - the line position in text layout progress. See `ASTextLinePositionSimpleModifier` for example. - */ -@protocol ASTextLinePositionModifier -@required -/** - This method will called before layout is completed. The method should be thread-safe. - @param lines An array of ASTextLine. - @param text The full text. - @param container The layout container. - */ -- (void)modifyLines:(NSArray *)lines fromText:(NSAttributedString *)text inContainer:(ASTextContainer *)container; -@end - - -/** - A simple implementation of `ASTextLinePositionModifier`. It can fix each line's position - to a specified value, lets each line of height be the same. - */ -@interface ASTextLinePositionSimpleModifier : NSObject -@property CGFloat fixedLineHeight; ///< The fixed line height (distance between two baseline). -@end - - - -/** - ASTextLayout class is a readonly class stores text layout result. - All the property in this class is readonly, and should not be changed. - The methods in this class is thread-safe (except some of the draw methods). - - example: (layout with a circle exclusion path) - - ┌──────────────────────────┐ <------ container - │ [--------Line0--------] │ <- Row0 - │ [--------Line1--------] │ <- Row1 - │ [-Line2-] [-Line3-] │ <- Row2 - │ [-Line4] [Line5-] │ <- Row3 - │ [-Line6-] [-Line7-] │ <- Row4 - │ [--------Line8--------] │ <- Row5 - │ [--------Line9--------] │ <- Row6 - └──────────────────────────┘ - */ -@interface ASTextLayout : NSObject - - -#pragma mark - Generate text layout -///============================================================================= -/// @name Generate text layout -///============================================================================= - -/** - Generate a layout with the given container size and text. - - @param size The text container's size - @param text The text (if nil, returns nil). - @return A new layout, or nil when an error occurs. - */ -+ (nullable ASTextLayout *)layoutWithContainerSize:(CGSize)size text:(NSAttributedString *)text; - -/** - Generate a layout with the given container and text. - - @param container The text container (if nil, returns nil). - @param text The text (if nil, returns nil). - @return A new layout, or nil when an error occurs. - */ -+ (nullable ASTextLayout *)layoutWithContainer:(ASTextContainer *)container text:(NSAttributedString *)text; - -/** - Generate a layout with the given container and text. - - @param container The text container (if nil, returns nil). - @param text The text (if nil, returns nil). - @param range The text range (if out of range, returns nil). If the - length of the range is 0, it means the length is no limit. - @return A new layout, or nil when an error occurs. - */ -+ (nullable ASTextLayout *)layoutWithContainer:(ASTextContainer *)container text:(NSAttributedString *)text range:(NSRange)range; - -/** - Generate layouts with the given containers and text. - - @param containers An array of ASTextContainer object (if nil, returns nil). - @param text The text (if nil, returns nil). - @return An array of ASTextLayout object (the count is same as containers), - or nil when an error occurs. - */ -+ (nullable NSArray *)layoutWithContainers:(NSArray *)containers - text:(NSAttributedString *)text; - -/** - Generate layouts with the given containers and text. - - @param containers An array of ASTextContainer object (if nil, returns nil). - @param text The text (if nil, returns nil). - @param range The text range (if out of range, returns nil). If the - length of the range is 0, it means the length is no limit. - @return An array of ASTextLayout object (the count is same as containers), - or nil when an error occurs. - */ -+ (nullable NSArray *)layoutWithContainers:(NSArray *)containers - text:(NSAttributedString *)text - range:(NSRange)range; - -- (instancetype)init UNAVAILABLE_ATTRIBUTE; -+ (instancetype)new UNAVAILABLE_ATTRIBUTE; - - -#pragma mark - Text layout attributes -///============================================================================= -/// @name Text layout attributes -///============================================================================= - -///< The text container -@property (nonatomic, readonly) ASTextContainer *container; -///< The full text -@property (nonatomic, readonly) NSAttributedString *text; -///< The text range in full text -@property (nonatomic, readonly) NSRange range; -///< CTFrame -@property (nonatomic, readonly) CTFrameRef frame; -///< Array of `ASTextLine`, no truncated -@property (nonatomic, readonly) NSArray *lines; -///< ASTextLine with truncated token, or nil -@property (nullable, nonatomic, readonly) ASTextLine *truncatedLine; -///< Array of `ASTextAttachment` -@property (nullable, nonatomic, readonly) NSArray *attachments; -///< Array of NSRange(wrapped by NSValue) in text -@property (nullable, nonatomic, readonly) NSArray *attachmentRanges; -///< Array of CGRect(wrapped by NSValue) in container -@property (nullable, nonatomic, readonly) NSArray *attachmentRects; -///< Set of Attachment (UIImage/UIView/CALayer) -@property (nullable, nonatomic, readonly) NSSet *attachmentContentsSet; -///< Number of rows -@property (nonatomic, readonly) NSUInteger rowCount; -///< Visible text range -@property (nonatomic, readonly) NSRange visibleRange; -///< Bounding rect (glyphs) -@property (nonatomic, readonly) CGRect textBoundingRect; -///< Bounding size (glyphs and insets, ceil to pixel) -@property (nonatomic, readonly) CGSize textBoundingSize; -///< Has highlight attribute -@property (nonatomic, readonly) BOOL containsHighlight; -///< Has block border attribute -@property (nonatomic, readonly) BOOL needDrawBlockBorder; -///< Has background border attribute -@property (nonatomic, readonly) BOOL needDrawBackgroundBorder; -///< Has shadow attribute -@property (nonatomic, readonly) BOOL needDrawShadow; -///< Has underline attribute -@property (nonatomic, readonly) BOOL needDrawUnderline; -///< Has visible text -@property (nonatomic, readonly) BOOL needDrawText; -///< Has attachment attribute -@property (nonatomic, readonly) BOOL needDrawAttachment; -///< Has inner shadow attribute -@property (nonatomic, readonly) BOOL needDrawInnerShadow; -///< Has strickthrough attribute -@property (nonatomic, readonly) BOOL needDrawStrikethrough; -///< Has border attribute -@property (nonatomic, readonly) BOOL needDrawBorder; - - -#pragma mark - Query information from text layout -///============================================================================= -/// @name Query information from text layout -///============================================================================= - -/** - The first line index for row. - - @param row A row index. - @return The line index, or NSNotFound if not found. - */ -- (NSUInteger)lineIndexForRow:(NSUInteger)row; - -/** - The number of lines for row. - - @param row A row index. - @return The number of lines, or NSNotFound when an error occurs. - */ -- (NSUInteger)lineCountForRow:(NSUInteger)row; - -/** - The row index for line. - - @param line A row index. - - @return The row index, or NSNotFound if not found. - */ -- (NSUInteger)rowIndexForLine:(NSUInteger)line; - -/** - The line index for a specified point. - - @discussion It returns NSNotFound if there's no text at the point. - - @param point A point in the container. - @return The line index, or NSNotFound if not found. - */ -- (NSUInteger)lineIndexForPoint:(CGPoint)point; - -/** - The line index closest to a specified point. - - @param point A point in the container. - @return The line index, or NSNotFound if no line exist in layout. - */ -- (NSUInteger)closestLineIndexForPoint:(CGPoint)point; - -/** - The offset in container for a text position in a specified line. - - @discussion The offset is the text position's baseline point.x. - If the container is vertical form, the offset is the baseline point.y; - - @param position The text position in string. - @param lineIndex The line index. - @return The offset in container, or CGFLOAT_MAX if not found. - */ -- (CGFloat)offsetForTextPosition:(NSUInteger)position lineIndex:(NSUInteger)lineIndex; - -/** - The text position for a point in a specified line. - - @discussion This method just call CTLineGetStringIndexForPosition() and does - NOT consider the emoji, line break character, binding text... - - @param point A point in the container. - @param lineIndex The line index. - @return The text position, or NSNotFound if not found. - */ -- (NSUInteger)textPositionForPoint:(CGPoint)point lineIndex:(NSUInteger)lineIndex; - -/** - The closest text position to a specified point. - - @discussion This method takes into account the restrict of emoji, line break - character, binding text and text affinity. - - @param point A point in the container. - @return A text position, or nil if not found. - */ -- (nullable ASTextPosition *)closestPositionToPoint:(CGPoint)point; - -/** - Returns the new position when moving selection grabber in text view. - - @discussion There are two grabber in the text selection period, user can only - move one grabber at the same time. - - @param point A point in the container. - @param oldPosition The old text position for the moving grabber. - @param otherPosition The other position in text selection view. - - @return A text position, or nil if not found. - */ -- (nullable ASTextPosition *)positionForPoint:(CGPoint)point - oldPosition:(ASTextPosition *)oldPosition - otherPosition:(ASTextPosition *)otherPosition; - -/** - Returns the character or range of characters that is at a given point in the container. - If there is no text at the point, returns nil. - - @discussion This method takes into account the restrict of emoji, line break - character, binding text and text affinity. - - @param point A point in the container. - @return An object representing a range that encloses a character (or characters) - at point. Or nil if not found. - */ -- (nullable ASTextRange *)textRangeAtPoint:(CGPoint)point; - -/** - Returns the closest character or range of characters that is at a given point in - the container. - - @discussion This method takes into account the restrict of emoji, line break - character, binding text and text affinity. - - @param point A point in the container. - @return An object representing a range that encloses a character (or characters) - at point. Or nil if not found. - */ -- (nullable ASTextRange *)closestTextRangeAtPoint:(CGPoint)point; - -/** - If the position is inside an emoji, composed character sequences, line break '\\r\\n' - or custom binding range, then returns the range by extend the position. Otherwise, - returns a zero length range from the position. - - @param position A text-position object that identifies a location in layout. - - @return A text-range object that extend the position. Or nil if an error occurs - */ -- (nullable ASTextRange *)textRangeByExtendingPosition:(ASTextPosition *)position; - -/** - Returns a text range at a given offset in a specified direction from another - text position to its farthest extent in a certain direction of layout. - - @param position A text-position object that identifies a location in layout. - @param direction A constant that indicates a direction of layout (right, left, up, down). - @param offset A character offset from position. - - @return A text-range object that represents the distance from position to the - farthest extent in direction. Or nil if an error occurs. - */ -- (nullable ASTextRange *)textRangeByExtendingPosition:(ASTextPosition *)position - inDirection:(UITextLayoutDirection)direction - offset:(NSInteger)offset; - -/** - Returns the line index for a given text position. - - @discussion This method takes into account the text affinity. - - @param position A text-position object that identifies a location in layout. - @return The line index, or NSNotFound if not found. - */ -- (NSUInteger)lineIndexForPosition:(ASTextPosition *)position; - -/** - Returns the baseline position for a given text position. - - @param position An object that identifies a location in the layout. - @return The baseline position for text, or CGPointZero if not found. - */ -- (CGPoint)linePositionForPosition:(ASTextPosition *)position; - -/** - Returns a rectangle used to draw the caret at a given insertion point. - - @param position An object that identifies a location in the layout. - @return A rectangle that defines the area for drawing the caret. The width is - always zero in normal container, the height is always zero in vertical form container. - If not found, it returns CGRectNull. - */ -- (CGRect)caretRectForPosition:(ASTextPosition *)position; - -/** - Returns the first rectangle that encloses a range of text in the layout. - - @param range An object that represents a range of text in layout. - - @return The first rectangle in a range of text. You might use this rectangle to - draw a correction rectangle. The "first" in the name refers the rectangle - enclosing the first line when the range encompasses multiple lines of text. - If not found, it returns CGRectNull. - */ -- (CGRect)firstRectForRange:(ASTextRange *)range; - -/** - Returns the rectangle union that encloses a range of text in the layout. - - @param range An object that represents a range of text in layout. - - @return A rectangle that defines the area than encloses the range. - If not found, it returns CGRectNull. - */ -- (CGRect)rectForRange:(ASTextRange *)range; - -/** - Returns an array of selection rects corresponding to the range of text. - The start and end rect can be used to show grabber. - - @param range An object representing a range in text. - @return An array of `ASTextSelectionRect` objects that encompass the selection. - If not found, the array is empty. - */ -- (NSArray *)selectionRectsForRange:(ASTextRange *)range; - -/** - Returns an array of selection rects corresponding to the range of text. - - @param range An object representing a range in text. - @return An array of `ASTextSelectionRect` objects that encompass the selection. - If not found, the array is empty. - */ -- (NSArray *)selectionRectsWithoutStartAndEndForRange:(ASTextRange *)range; - -/** - Returns the start and end selection rects corresponding to the range of text. - The start and end rect can be used to show grabber. - - @param range An object representing a range in text. - @return An array of `ASTextSelectionRect` objects contains the start and end to - the selection. If not found, the array is empty. - */ -- (NSArray *)selectionRectsWithOnlyStartAndEndForRange:(ASTextRange *)range; - - -#pragma mark - Draw text layout -///============================================================================= -/// @name Draw text layout -///============================================================================= - -/** - Draw the layout and show the attachments. - - @discussion If the `view` parameter is not nil, then the attachment views will - add to this `view`, and if the `layer` parameter is not nil, then the attachment - layers will add to this `layer`. - - @warning This method should be called on main thread if `view` or `layer` parameter - is not nil and there's UIView or CALayer attachments in layout. - Otherwise, it can be called on any thread. - - @param context The draw context. Pass nil to avoid text and image drawing. - @param size The context size. - @param point The point at which to draw the layout. - @param view The attachment views will add to this view. - @param layer The attachment layers will add to this layer. - @param debug The debug option. Pass nil to avoid debug drawing. - @param cancel The cancel checker block. It will be called in drawing progress. - If it returns YES, the further draw progress will be canceled. - Pass nil to ignore this feature. - */ -- (void)drawInContext:(nullable CGContextRef)context - size:(CGSize)size - point:(CGPoint)point - view:(nullable UIView *)view - layer:(nullable CALayer *)layer - debug:(nullable ASTextDebugOption *)debug - cancel:(nullable BOOL (^)(void))cancel; - -/** - Draw the layout text and image (without view or layer attachments). - - @discussion This method is thread safe and can be called on any thread. - - @param context The draw context. Pass nil to avoid text and image drawing. - @param size The context size. - @param debug The debug option. Pass nil to avoid debug drawing. - */ -- (void)drawInContext:(nullable CGContextRef)context - size:(CGSize)size - debug:(nullable ASTextDebugOption *)debug; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Component/ASTextLayout.mm b/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Component/ASTextLayout.mm deleted file mode 100644 index 135709a845..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Component/ASTextLayout.mm +++ /dev/null @@ -1,3485 +0,0 @@ -// -// ASTextLayout.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import -#import -#import -#import -#import - -#import - -const CGSize ASTextContainerMaxSize = (CGSize){0x100000, 0x100000}; - -typedef struct { - CGFloat head; - CGFloat foot; -} ASRowEdge; - -static inline CGSize ASTextClipCGSize(CGSize size) { - if (size.width > ASTextContainerMaxSize.width) size.width = ASTextContainerMaxSize.width; - if (size.height > ASTextContainerMaxSize.height) size.height = ASTextContainerMaxSize.height; - return size; -} - -static inline UIEdgeInsets UIEdgeInsetRotateVertical(UIEdgeInsets insets) { - UIEdgeInsets one; - one.top = insets.left; - one.left = insets.bottom; - one.bottom = insets.right; - one.right = insets.top; - return one; -} - -/** - Sometimes CoreText may convert CGColor to UIColor for `kCTForegroundColorAttributeName` - attribute in iOS7. This should be a bug of CoreText, and may cause crash. Here's a workaround. - */ -static CGColorRef ASTextGetCGColor(CGColorRef color) { - static UIColor *defaultColor; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - defaultColor = [UIColor blackColor]; - }); - if (!color) return defaultColor.CGColor; - if ([((__bridge NSObject *)color) respondsToSelector:@selector(CGColor)]) { - return ((__bridge UIColor *)color).CGColor; - } - return color; -} - -@implementation ASTextLinePositionSimpleModifier -- (void)modifyLines:(NSArray *)lines fromText:(NSAttributedString *)text inContainer:(ASTextContainer *)container { - if (container.verticalForm) { - for (NSUInteger i = 0, max = lines.count; i < max; i++) { - ASTextLine *line = lines[i]; - CGPoint pos = line.position; - pos.x = container.size.width - container.insets.right - line.row * _fixedLineHeight - _fixedLineHeight * 0.9; - line.position = pos; - } - } else { - for (NSUInteger i = 0, max = lines.count; i < max; i++) { - ASTextLine *line = lines[i]; - CGPoint pos = line.position; - pos.y = line.row * _fixedLineHeight + _fixedLineHeight * 0.9 + container.insets.top; - line.position = pos; - } - } -} - -- (id)copyWithZone:(NSZone *)zone { - ASTextLinePositionSimpleModifier *one = [self.class new]; - one.fixedLineHeight = _fixedLineHeight; - return one; -} -@end - - -@implementation ASTextContainer { - @package - BOOL _readonly; ///< used only in ASTextLayout.implementation - dispatch_semaphore_t _lock; - - CGSize _size; - UIEdgeInsets _insets; - UIBezierPath *_path; - NSArray *_exclusionPaths; - BOOL _pathFillEvenOdd; - CGFloat _pathLineWidth; - BOOL _verticalForm; - NSUInteger _maximumNumberOfRows; - ASTextTruncationType _truncationType; - NSAttributedString *_truncationToken; - id _linePositionModifier; -} - -- (NSString *)description -{ - return [NSString - stringWithFormat:@"immutable: %@, insets: %@, size: %@", self->_readonly ? @"YES" : @"NO", - NSStringFromUIEdgeInsets(self->_insets), NSStringFromCGSize(self->_size)]; -} - -+ (instancetype)containerWithSize:(CGSize)size NS_RETURNS_RETAINED { - return [self containerWithSize:size insets:UIEdgeInsetsZero]; -} - -+ (instancetype)containerWithSize:(CGSize)size insets:(UIEdgeInsets)insets NS_RETURNS_RETAINED { - ASTextContainer *one = [self new]; - one.size = ASTextClipCGSize(size); - one.insets = insets; - return one; -} - -+ (instancetype)containerWithPath:(UIBezierPath *)path NS_RETURNS_RETAINED { - ASTextContainer *one = [self new]; - one.path = path; - return one; -} - -- (instancetype)init { - self = [super init]; - if (!self) return nil; - _lock = dispatch_semaphore_create(1); - _pathFillEvenOdd = YES; - return self; -} - -- (id)copyForced:(BOOL)forceCopy -{ - dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); - if (_readonly && !forceCopy) { - dispatch_semaphore_signal(_lock); - return self; - } - - ASTextContainer *one = [self.class new]; - one->_size = _size; - one->_insets = _insets; - one->_path = _path; - one->_exclusionPaths = [_exclusionPaths copy]; - one->_pathFillEvenOdd = _pathFillEvenOdd; - one->_pathLineWidth = _pathLineWidth; - one->_verticalForm = _verticalForm; - one->_maximumNumberOfRows = _maximumNumberOfRows; - one->_truncationType = _truncationType; - one->_truncationToken = [_truncationToken copy]; - one->_linePositionModifier = [(NSObject *)_linePositionModifier copy]; - dispatch_semaphore_signal(_lock); - return one; -} - -- (id)copyWithZone:(NSZone *)zone { - return [self copyForced:NO]; -} - -- (id)mutableCopyWithZone:(NSZone *)zone { - return [self copyForced:YES]; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder { - [aCoder encodeObject:[NSValue valueWithCGSize:_size] forKey:@"size"]; - [aCoder encodeObject:[NSValue valueWithUIEdgeInsets:_insets] forKey:@"insets"]; - [aCoder encodeObject:_path forKey:@"path"]; - [aCoder encodeObject:_exclusionPaths forKey:@"exclusionPaths"]; - [aCoder encodeBool:_pathFillEvenOdd forKey:@"pathFillEvenOdd"]; - [aCoder encodeDouble:_pathLineWidth forKey:@"pathLineWidth"]; - [aCoder encodeBool:_verticalForm forKey:@"verticalForm"]; - [aCoder encodeInteger:_maximumNumberOfRows forKey:@"maximumNumberOfRows"]; - [aCoder encodeInteger:_truncationType forKey:@"truncationType"]; - [aCoder encodeObject:_truncationToken forKey:@"truncationToken"]; - if ([_linePositionModifier respondsToSelector:@selector(encodeWithCoder:)] && - [_linePositionModifier respondsToSelector:@selector(initWithCoder:)]) { - [aCoder encodeObject:_linePositionModifier forKey:@"linePositionModifier"]; - } -} - -- (id)initWithCoder:(NSCoder *)aDecoder { - self = [self init]; - _size = ((NSValue *)[aDecoder decodeObjectForKey:@"size"]).CGSizeValue; - _insets = ((NSValue *)[aDecoder decodeObjectForKey:@"insets"]).UIEdgeInsetsValue; - _path = [aDecoder decodeObjectForKey:@"path"]; - _exclusionPaths = [aDecoder decodeObjectForKey:@"exclusionPaths"]; - _pathFillEvenOdd = [aDecoder decodeBoolForKey:@"pathFillEvenOdd"]; - _pathLineWidth = [aDecoder decodeDoubleForKey:@"pathLineWidth"]; - _verticalForm = [aDecoder decodeBoolForKey:@"verticalForm"]; - _maximumNumberOfRows = [aDecoder decodeIntegerForKey:@"maximumNumberOfRows"]; - _truncationType = (ASTextTruncationType)[aDecoder decodeIntegerForKey:@"truncationType"]; - _truncationToken = [aDecoder decodeObjectForKey:@"truncationToken"]; - _linePositionModifier = [aDecoder decodeObjectForKey:@"linePositionModifier"]; - return self; -} - -- (void)makeImmutable -{ - dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); - _readonly = YES; - dispatch_semaphore_signal(_lock); -} - -#define Getter(...) \ -dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); \ -__VA_ARGS__; \ -dispatch_semaphore_signal(_lock); - -#define Setter(...) \ -dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); \ -if (__builtin_expect(_readonly, NO)) { \ - ASDisplayNodeFailAssert(@"Attempt to modify immutable text container."); \ - dispatch_semaphore_signal(_lock); \ - return; \ -} \ -__VA_ARGS__; \ -dispatch_semaphore_signal(_lock); - -- (CGSize)size { - Getter(CGSize size = _size) return size; -} - -- (void)setSize:(CGSize)size { - Setter(if(!_path) _size = ASTextClipCGSize(size)); -} - -- (UIEdgeInsets)insets { - Getter(UIEdgeInsets insets = _insets) return insets; -} - -- (void)setInsets:(UIEdgeInsets)insets { - Setter(if(!_path){ - if (insets.top < 0) insets.top = 0; - if (insets.left < 0) insets.left = 0; - if (insets.bottom < 0) insets.bottom = 0; - if (insets.right < 0) insets.right = 0; - _insets = insets; - }); -} - -- (UIBezierPath *)path { - Getter(UIBezierPath *path = _path) return path; -} - -- (void)setPath:(UIBezierPath *)path { - Setter( - _path = path.copy; - if (_path) { - CGRect bounds = _path.bounds; - CGSize size = bounds.size; - UIEdgeInsets insets = UIEdgeInsetsZero; - if (bounds.origin.x < 0) size.width += bounds.origin.x; - if (bounds.origin.x > 0) insets.left = bounds.origin.x; - if (bounds.origin.y < 0) size.height += bounds.origin.y; - if (bounds.origin.y > 0) insets.top = bounds.origin.y; - _size = size; - _insets = insets; - } - ); -} - -- (NSArray *)exclusionPaths { - Getter(NSArray *paths = _exclusionPaths) return paths; -} - -- (void)setExclusionPaths:(NSArray *)exclusionPaths { - Setter(_exclusionPaths = exclusionPaths.copy); -} - -- (BOOL)isPathFillEvenOdd { - Getter(BOOL is = _pathFillEvenOdd) return is; -} - -- (void)setPathFillEvenOdd:(BOOL)pathFillEvenOdd { - Setter(_pathFillEvenOdd = pathFillEvenOdd); -} - -- (CGFloat)pathLineWidth { - Getter(CGFloat width = _pathLineWidth) return width; -} - -- (void)setPathLineWidth:(CGFloat)pathLineWidth { - Setter(_pathLineWidth = pathLineWidth); -} - -- (BOOL)isVerticalForm { - Getter(BOOL v = _verticalForm) return v; -} - -- (void)setVerticalForm:(BOOL)verticalForm { - Setter(_verticalForm = verticalForm); -} - -- (NSUInteger)maximumNumberOfRows { - Getter(NSUInteger num = _maximumNumberOfRows) return num; -} - -- (void)setMaximumNumberOfRows:(NSUInteger)maximumNumberOfRows { - Setter(_maximumNumberOfRows = maximumNumberOfRows); -} - -- (ASTextTruncationType)truncationType { - Getter(ASTextTruncationType type = _truncationType) return type; -} - -- (void)setTruncationType:(ASTextTruncationType)truncationType { - Setter(_truncationType = truncationType); -} - -- (NSAttributedString *)truncationToken { - Getter(NSAttributedString *token = _truncationToken) return token; -} - -- (void)setTruncationToken:(NSAttributedString *)truncationToken { - Setter(_truncationToken = truncationToken.copy); -} - -- (void)setLinePositionModifier:(id)linePositionModifier { - Setter(_linePositionModifier = [(NSObject *)linePositionModifier copy]); -} - -- (id)linePositionModifier { - Getter(id m = _linePositionModifier) return m; -} - -#undef Getter -#undef Setter -@end - - - - -@interface ASTextLayout () - -@property (nonatomic) ASTextContainer *container; -@property (nonatomic) NSAttributedString *text; -@property (nonatomic) NSRange range; - -@property (nonatomic) CTFrameRef frame; -@property (nonatomic) NSArray *lines; -@property (nonatomic) ASTextLine *truncatedLine; -@property (nonatomic) NSArray *attachments; -@property (nonatomic) NSArray *attachmentRanges; -@property (nonatomic) NSArray *attachmentRects; -@property (nonatomic) NSSet *attachmentContentsSet; -@property (nonatomic) NSUInteger rowCount; -@property (nonatomic) NSRange visibleRange; -@property (nonatomic) CGRect textBoundingRect; -@property (nonatomic) CGSize textBoundingSize; - -@property (nonatomic) BOOL containsHighlight; -@property (nonatomic) BOOL needDrawBlockBorder; -@property (nonatomic) BOOL needDrawBackgroundBorder; -@property (nonatomic) BOOL needDrawShadow; -@property (nonatomic) BOOL needDrawUnderline; -@property (nonatomic) BOOL needDrawText; -@property (nonatomic) BOOL needDrawAttachment; -@property (nonatomic) BOOL needDrawInnerShadow; -@property (nonatomic) BOOL needDrawStrikethrough; -@property (nonatomic) BOOL needDrawBorder; - -@property (nonatomic) NSUInteger *lineRowsIndex; -@property (nonatomic) ASRowEdge *lineRowsEdge; ///< top-left origin - -@end - - - -@implementation ASTextLayout - -#pragma mark - Layout - -- (instancetype)_init { - self = [super init]; - return self; -} - -- (NSString *)description -{ - return [NSString stringWithFormat:@"lines: %ld, visibleRange:%@, textBoundingRect:%@", - [self.lines count], - NSStringFromRange(self.visibleRange), - NSStringFromCGRect(self.textBoundingRect)]; -} - -+ (ASTextLayout *)layoutWithContainerSize:(CGSize)size text:(NSAttributedString *)text { - ASTextContainer *container = [ASTextContainer containerWithSize:size]; - return [self layoutWithContainer:container text:text]; -} - -+ (ASTextLayout *)layoutWithContainer:(ASTextContainer *)container text:(NSAttributedString *)text { - return [self layoutWithContainer:container text:text range:NSMakeRange(0, text.length)]; -} - -+ (ASTextLayout *)layoutWithContainer:(ASTextContainer *)container text:(NSAttributedString *)text range:(NSRange)range { - ASTextLayout *layout = NULL; - CGPathRef cgPath = nil; - CGRect cgPathBox = {0}; - BOOL isVerticalForm = NO; - BOOL rowMaySeparated = NO; - NSMutableDictionary *frameAttrs = nil; - CTFramesetterRef ctSetter = NULL; - CTFrameRef ctFrame = NULL; - CFArrayRef ctLines = nil; - CGPoint *lineOrigins = NULL; - NSUInteger lineCount = 0; - NSMutableArray *lines = nil; - NSMutableArray *attachments = nil; - NSMutableArray *attachmentRanges = nil; - NSMutableArray *attachmentRects = nil; - NSMutableSet *attachmentContentsSet = nil; - BOOL needTruncation = NO; - NSAttributedString *truncationToken = nil; - ASTextLine *truncatedLine = nil; - ASRowEdge *lineRowsEdge = NULL; - NSUInteger *lineRowsIndex = NULL; - NSRange visibleRange; - NSUInteger maximumNumberOfRows = 0; - BOOL constraintSizeIsExtended = NO; - CGRect constraintRectBeforeExtended = {0}; -#define FAIL_AND_RETURN {\ - if (cgPath) CFRelease(cgPath); \ - if (ctSetter) CFRelease(ctSetter); \ - if (ctFrame) CFRelease(ctFrame); \ - if (lineOrigins) free(lineOrigins); \ - if (lineRowsEdge) free(lineRowsEdge); \ - if (lineRowsIndex) free(lineRowsIndex); \ - return nil; } - - container = [container copy]; - if (!text || !container) return nil; - if (range.location + range.length > text.length) return nil; - [container makeImmutable]; - maximumNumberOfRows = container.maximumNumberOfRows; - - // It may use larger constraint size when create CTFrame with - // CTFramesetterCreateFrame in iOS 10. - BOOL needFixLayoutSizeBug = AS_AT_LEAST_IOS10; - - layout = [[ASTextLayout alloc] _init]; - layout.text = text; - layout.container = container; - layout.range = range; - isVerticalForm = container.verticalForm; - - // set cgPath and cgPathBox - if (container.path == nil && container.exclusionPaths.count == 0) { - if (container.size.width <= 0 || container.size.height <= 0) FAIL_AND_RETURN - CGRect rect = (CGRect) {CGPointZero, container.size }; - if (needFixLayoutSizeBug) { - constraintSizeIsExtended = YES; - constraintRectBeforeExtended = UIEdgeInsetsInsetRect(rect, container.insets); - constraintRectBeforeExtended = CGRectStandardize(constraintRectBeforeExtended); - if (container.isVerticalForm) { - rect.size.width = ASTextContainerMaxSize.width; - } else { - rect.size.height = ASTextContainerMaxSize.height; - } - } - rect = UIEdgeInsetsInsetRect(rect, container.insets); - rect = CGRectStandardize(rect); - cgPathBox = rect; - rect = CGRectApplyAffineTransform(rect, CGAffineTransformMakeScale(1, -1)); - cgPath = CGPathCreateWithRect(rect, NULL); // let CGPathIsRect() returns true - } else if (container.path && CGPathIsRect(container.path.CGPath, &cgPathBox) && container.exclusionPaths.count == 0) { - CGRect rect = CGRectApplyAffineTransform(cgPathBox, CGAffineTransformMakeScale(1, -1)); - cgPath = CGPathCreateWithRect(rect, NULL); // let CGPathIsRect() returns true - } else { - rowMaySeparated = YES; - CGMutablePathRef path = NULL; - if (container.path) { - path = CGPathCreateMutableCopy(container.path.CGPath); - } else { - CGRect rect = (CGRect) {CGPointZero, container.size }; - rect = UIEdgeInsetsInsetRect(rect, container.insets); - CGPathRef rectPath = CGPathCreateWithRect(rect, NULL); - if (rectPath) { - path = CGPathCreateMutableCopy(rectPath); - CGPathRelease(rectPath); - } - } - if (path) { - [layout.container.exclusionPaths enumerateObjectsUsingBlock: ^(UIBezierPath *onePath, NSUInteger idx, BOOL *stop) { - CGPathAddPath(path, NULL, onePath.CGPath); - }]; - - cgPathBox = CGPathGetPathBoundingBox(path); - CGAffineTransform trans = CGAffineTransformMakeScale(1, -1); - CGMutablePathRef transPath = CGPathCreateMutableCopyByTransformingPath(path, &trans); - CGPathRelease(path); - path = transPath; - } - cgPath = path; - } - if (!cgPath) FAIL_AND_RETURN - - // frame setter config - frameAttrs = [[NSMutableDictionary alloc] init]; - if (container.isPathFillEvenOdd == NO) { - frameAttrs[(id)kCTFramePathFillRuleAttributeName] = @(kCTFramePathFillWindingNumber); - } - if (container.pathLineWidth > 0) { - frameAttrs[(id)kCTFramePathWidthAttributeName] = @(container.pathLineWidth); - } - if (container.isVerticalForm == YES) { - frameAttrs[(id)kCTFrameProgressionAttributeName] = @(kCTFrameProgressionRightToLeft); - } - - /* - * Framesetter cache. - * Framesetters can only be used by one thread at a time. - * Create a CFSet with no callbacks (raw pointers) to keep track of which - * framesetters are in use on other threads. If the one for our string is already in use, - * just create a new one. This should be pretty rare. - */ - static pthread_mutex_t busyFramesettersLock = PTHREAD_MUTEX_INITIALIZER; - static NSCache *framesetterCache; - static CFMutableSetRef busyFramesetters; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - if (ASActivateExperimentalFeature(ASExperimentalFramesetterCache)) { - framesetterCache = [[NSCache alloc] init]; - framesetterCache.name = @"org.TextureGroup.Texture.framesetterCache"; - busyFramesetters = CFSetCreateMutable(NULL, 0, NULL); - } - }); - - BOOL haveCached = NO, useCached = NO; - if (framesetterCache) { - // Check if there's one in the cache. - ctSetter = (__bridge_retained CTFramesetterRef)[framesetterCache objectForKey:text]; - - if (ctSetter) { - haveCached = YES; - - // Check-and-set busy on the cached one. - pthread_mutex_lock(&busyFramesettersLock); - BOOL busy = CFSetContainsValue(busyFramesetters, ctSetter); - if (!busy) { - CFSetAddValue(busyFramesetters, ctSetter); - useCached = YES; - } - pthread_mutex_unlock(&busyFramesettersLock); - - // Release if it was busy. - if (busy) { - CFRelease(ctSetter); - ctSetter = NULL; - } - } - } - - // Create a framesetter if needed. - if (!ctSetter) { - ctSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)text); - } - - if (!ctSetter) FAIL_AND_RETURN - ctFrame = CTFramesetterCreateFrame(ctSetter, ASTextCFRangeFromNSRange(range), cgPath, (CFDictionaryRef)frameAttrs); - - // Return to cache. - if (framesetterCache) { - if (useCached) { - // If reused: mark available. - pthread_mutex_lock(&busyFramesettersLock); - CFSetRemoveValue(busyFramesetters, ctSetter); - pthread_mutex_unlock(&busyFramesettersLock); - } else if (!haveCached) { - // If first framesetter, add to cache. - [framesetterCache setObject:(__bridge id)ctSetter forKey:text]; - } - } - - if (!ctFrame) FAIL_AND_RETURN - lines = [NSMutableArray new]; - ctLines = CTFrameGetLines(ctFrame); - lineCount = CFArrayGetCount(ctLines); - if (lineCount > 0) { - lineOrigins = (CGPoint *)malloc(lineCount * sizeof(CGPoint)); - if (lineOrigins == NULL) FAIL_AND_RETURN - CTFrameGetLineOrigins(ctFrame, CFRangeMake(0, lineCount), lineOrigins); - } - - CGRect textBoundingRect = CGRectZero; - CGSize textBoundingSize = CGSizeZero; - NSInteger rowIdx = -1; - NSUInteger rowCount = 0; - CGRect lastRect = CGRectMake(0, -FLT_MAX, 0, 0); - CGPoint lastPosition = CGPointMake(0, -FLT_MAX); - if (isVerticalForm) { - lastRect = CGRectMake(FLT_MAX, 0, 0, 0); - lastPosition = CGPointMake(FLT_MAX, 0); - } - - // calculate line frame - NSUInteger lineCurrentIdx = 0; - BOOL measuringBeyondConstraints = NO; - for (NSUInteger i = 0; i < lineCount; i++) { - CTLineRef ctLine = (CTLineRef)CFArrayGetValueAtIndex(ctLines, i); - CFArrayRef ctRuns = CTLineGetGlyphRuns(ctLine); - if (!ctRuns || CFArrayGetCount(ctRuns) == 0) continue; - - // CoreText coordinate system - CGPoint ctLineOrigin = lineOrigins[i]; - - // UIKit coordinate system - CGPoint position; - position.x = cgPathBox.origin.x + ctLineOrigin.x; - position.y = cgPathBox.size.height + cgPathBox.origin.y - ctLineOrigin.y; - - ASTextLine *line = [ASTextLine lineWithCTLine:ctLine position:position vertical:isVerticalForm]; - - [lines addObject:line]; - } - - // Give user a chance to modify the line's position. - [container.linePositionModifier modifyLines:lines fromText:text inContainer:container]; - - BOOL first = YES; - for (ASTextLine *line in lines) { - CGPoint position = line.position; - CGRect rect = line.bounds; - if (constraintSizeIsExtended) { - if (isVerticalForm) { - if (rect.origin.x + rect.size.width > - constraintRectBeforeExtended.origin.x + - constraintRectBeforeExtended.size.width) { - measuringBeyondConstraints = YES; - } - } else { - if (rect.origin.y + rect.size.height > - constraintRectBeforeExtended.origin.y + - constraintRectBeforeExtended.size.height) { - measuringBeyondConstraints = YES; - } - } - } - - BOOL newRow = !measuringBeyondConstraints; - if (newRow && rowMaySeparated && position.x != lastPosition.x) { - if (isVerticalForm) { - if (rect.size.width > lastRect.size.width) { - if (rect.origin.x > lastPosition.x && lastPosition.x > rect.origin.x - rect.size.width) newRow = NO; - } else { - if (lastRect.origin.x > position.x && position.x > lastRect.origin.x - lastRect.size.width) newRow = NO; - } - } else { - if (rect.size.height > lastRect.size.height) { - if (rect.origin.y < lastPosition.y && lastPosition.y < rect.origin.y + rect.size.height) newRow = NO; - } else { - if (lastRect.origin.y < position.y && position.y < lastRect.origin.y + lastRect.size.height) newRow = NO; - } - } - } - - if (newRow) rowIdx++; - lastRect = rect; - lastPosition = position; - - line.index = lineCurrentIdx; - line.row = rowIdx; - - rowCount = rowIdx + 1; - lineCurrentIdx ++; - - if (first) { - first = NO; - textBoundingRect = rect; - } else if (!measuringBeyondConstraints) { - if (maximumNumberOfRows == 0 || rowIdx < maximumNumberOfRows) { - textBoundingRect = CGRectUnion(textBoundingRect, rect); - } - } - } - - { - NSMutableArray *removedLines = [NSMutableArray new]; - if (rowCount > 0) { - if (maximumNumberOfRows > 0) { - if (rowCount > maximumNumberOfRows) { - needTruncation = YES; - rowCount = maximumNumberOfRows; - do { - ASTextLine *line = lines.lastObject; - if (!line) break; - if (line.row < rowCount) break; // we have removed down to an allowed # of lines now - [lines removeLastObject]; - [removedLines addObject:line]; - } while (1); - } - } - ASTextLine *lastLine = rowCount < lines.count ? lines[rowCount - 1] : lines.lastObject; - if (!needTruncation && lastLine.range.location + lastLine.range.length < text.length) { - needTruncation = YES; - while (lines.count > rowCount) { - ASTextLine *line = lines.lastObject; - [lines removeLastObject]; - [removedLines addObject:line]; - } - } - - lineRowsEdge = (ASRowEdge *) calloc(rowCount, sizeof(ASRowEdge)); - if (lineRowsEdge == NULL) FAIL_AND_RETURN - lineRowsIndex = (NSUInteger *) calloc(rowCount, sizeof(NSUInteger)); - if (lineRowsIndex == NULL) FAIL_AND_RETURN - NSInteger lastRowIdx = -1; - CGFloat lastHead = 0; - CGFloat lastFoot = 0; - for (NSUInteger i = 0, max = lines.count; i < max; i++) { - ASTextLine *line = lines[i]; - CGRect rect = line.bounds; - if ((NSInteger) line.row != lastRowIdx) { - if (lastRowIdx >= 0) { - lineRowsEdge[lastRowIdx] = (ASRowEdge) {.head = lastHead, .foot = lastFoot}; - } - lastRowIdx = line.row; - lineRowsIndex[lastRowIdx] = i; - if (isVerticalForm) { - lastHead = rect.origin.x + rect.size.width; - lastFoot = lastHead - rect.size.width; - } else { - lastHead = rect.origin.y; - lastFoot = lastHead + rect.size.height; - } - } else { - if (isVerticalForm) { - lastHead = MAX(lastHead, rect.origin.x + rect.size.width); - lastFoot = MIN(lastFoot, rect.origin.x); - } else { - lastHead = MIN(lastHead, rect.origin.y); - lastFoot = MAX(lastFoot, rect.origin.y + rect.size.height); - } - } - } - lineRowsEdge[lastRowIdx] = (ASRowEdge) {.head = lastHead, .foot = lastFoot}; - - for (NSUInteger i = 1; i < rowCount; i++) { - ASRowEdge v0 = lineRowsEdge[i - 1]; - ASRowEdge v1 = lineRowsEdge[i]; - lineRowsEdge[i - 1].foot = lineRowsEdge[i].head = (v0.foot + v1.head) * 0.5; - } - } - - { // calculate bounding size - CGRect rect = textBoundingRect; - if (container.path) { - if (container.pathLineWidth > 0) { - CGFloat inset = container.pathLineWidth / 2; - rect = CGRectInset(rect, -inset, -inset); - } - } else { - rect = UIEdgeInsetsInsetRect(rect, ASTextUIEdgeInsetsInvert(container.insets)); - } - rect = CGRectStandardize(rect); - CGSize size = rect.size; - if (container.verticalForm) { - size.width += container.size.width - (rect.origin.x + rect.size.width); - } else { - size.width += rect.origin.x; - } - size.height += rect.origin.y; - if (size.width < 0) size.width = 0; - if (size.height < 0) size.height = 0; - size.width = ceil(size.width); - size.height = ceil(size.height); - textBoundingSize = size; - } - - visibleRange = ASTextNSRangeFromCFRange(CTFrameGetVisibleStringRange(ctFrame)); - if (needTruncation) { - ASTextLine *lastLine = lines.lastObject; - NSRange lastRange = lastLine.range; - visibleRange.length = lastRange.location + lastRange.length - visibleRange.location; - - // create truncated line - if (container.truncationType != ASTextTruncationTypeNone) { - CTLineRef truncationTokenLine = NULL; - if (container.truncationToken) { - truncationToken = container.truncationToken; - truncationTokenLine = CTLineCreateWithAttributedString((CFAttributedStringRef) truncationToken); - } else { - CFArrayRef runs = CTLineGetGlyphRuns(lastLine.CTLine); - NSUInteger runCount = CFArrayGetCount(runs); - NSMutableDictionary *attrs = nil; - if (runCount > 0) { - CTRunRef run = (CTRunRef) CFArrayGetValueAtIndex(runs, runCount - 1); - attrs = (id) CTRunGetAttributes(run); - attrs = attrs ? attrs.mutableCopy : [NSMutableArray new]; - [attrs removeObjectsForKeys:[NSMutableAttributedString as_allDiscontinuousAttributeKeys]]; - CTFontRef font = (__bridge CTFontRef) attrs[(id) kCTFontAttributeName]; - CGFloat fontSize = font ? CTFontGetSize(font) : 12.0; - UIFont *uiFont = [UIFont systemFontOfSize:fontSize * 0.9]; - if (uiFont) { - font = CTFontCreateWithName((__bridge CFStringRef) uiFont.fontName, uiFont.pointSize, NULL); - } else { - font = NULL; - } - if (font) { - attrs[(id) kCTFontAttributeName] = (__bridge id) (font); - uiFont = nil; - CFRelease(font); - } - CGColorRef color = (__bridge CGColorRef) (attrs[(id) kCTForegroundColorAttributeName]); - if (color && CFGetTypeID(color) == CGColorGetTypeID() && CGColorGetAlpha(color) == 0) { - // ignore clear color - [attrs removeObjectForKey:(id) kCTForegroundColorAttributeName]; - } - if (!attrs) attrs = [NSMutableDictionary new]; - } - truncationToken = [[NSAttributedString alloc] initWithString:ASTextTruncationToken attributes:attrs]; - truncationTokenLine = CTLineCreateWithAttributedString((CFAttributedStringRef) truncationToken); - } - if (truncationTokenLine) { - CTLineTruncationType type = kCTLineTruncationEnd; - if (container.truncationType == ASTextTruncationTypeStart) { - type = kCTLineTruncationStart; - } else if (container.truncationType == ASTextTruncationTypeMiddle) { - type = kCTLineTruncationMiddle; - } - NSMutableAttributedString *lastLineText = [text attributedSubstringFromRange:lastLine.range].mutableCopy; - CGFloat truncatedWidth = lastLine.width; - CGFloat atLeastOneLine = lastLine.width; - CGRect cgPathRect = CGRectZero; - if (CGPathIsRect(cgPath, &cgPathRect)) { - if (isVerticalForm) { - truncatedWidth = cgPathRect.size.height; - } else { - truncatedWidth = cgPathRect.size.width; - } - } - int i = 0; - if (type != kCTLineTruncationStart) { // Middle or End/Tail wants to collect some text (at least one line's - // worth) preceding the truncated content, with which to construct a "truncated line". - i = (int)removedLines.count - 1; - while (atLeastOneLine < truncatedWidth && i >= 0) { - if (lastLineText.length > 0 && [lastLineText.string characterAtIndex:lastLineText.string.length - 1] == '\n') { // Explicit newlines are always "long enough". - [lastLineText deleteCharactersInRange:NSMakeRange(lastLineText.string.length - 1, 1)]; - break; - } - [lastLineText appendAttributedString:[text attributedSubstringFromRange:removedLines[i].range]]; - atLeastOneLine += removedLines[i--].width; - } - [lastLineText appendAttributedString:truncationToken]; - } - if (type != kCTLineTruncationEnd && removedLines.count > 0) { // Middle or Start/Head wants to collect some - // text following the truncated content. - i = 0; - atLeastOneLine = removedLines[i].width; - while (atLeastOneLine < truncatedWidth && i < removedLines.count) { - atLeastOneLine += removedLines[i++].width; - } - for (i--; i >= 0; i--) { - NSAttributedString *nextLine = [text attributedSubstringFromRange:removedLines[i].range]; - if ([nextLine.string characterAtIndex:nextLine.string.length - 1] == '\n') { // Explicit newlines are always "long enough". - lastLineText = [NSMutableAttributedString new]; - } else { - [lastLineText appendAttributedString:nextLine]; - } - } - if (type == kCTLineTruncationStart) { - [lastLineText insertAttributedString:truncationToken atIndex:0]; - } - } - - CTLineRef ctLastLineExtend = CTLineCreateWithAttributedString((CFAttributedStringRef) lastLineText); - if (ctLastLineExtend) { - CTLineRef ctTruncatedLine = CTLineCreateTruncatedLine(ctLastLineExtend, truncatedWidth, type, truncationTokenLine); - CFRelease(ctLastLineExtend); - if (ctTruncatedLine) { - truncatedLine = [ASTextLine lineWithCTLine:ctTruncatedLine position:lastLine.position vertical:isVerticalForm]; - truncatedLine.index = lastLine.index; - truncatedLine.row = lastLine.row; - CFRelease(ctTruncatedLine); - } - } - CFRelease(truncationTokenLine); - } - } - } - } - - if (isVerticalForm) { - NSCharacterSet *rotateCharset = ASTextVerticalFormRotateCharacterSet(); - NSCharacterSet *rotateMoveCharset = ASTextVerticalFormRotateAndMoveCharacterSet(); - - void (^lineBlock)(ASTextLine *) = ^(ASTextLine *line){ - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - if (!runs) return; - NSUInteger runCount = CFArrayGetCount(runs); - if (runCount == 0) return; - NSMutableArray *lineRunRanges = [NSMutableArray new]; - line.verticalRotateRange = lineRunRanges; - for (NSUInteger r = 0; r < runCount; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - NSMutableArray *runRanges = [NSMutableArray new]; - [lineRunRanges addObject:runRanges]; - NSUInteger glyphCount = CTRunGetGlyphCount(run); - if (glyphCount == 0) continue; - - CFIndex runStrIdx[glyphCount + 1]; - CTRunGetStringIndices(run, CFRangeMake(0, 0), runStrIdx); - CFRange runStrRange = CTRunGetStringRange(run); - runStrIdx[glyphCount] = runStrRange.location + runStrRange.length; - CFDictionaryRef runAttrs = CTRunGetAttributes(run); - CTFontRef font = (CTFontRef)CFDictionaryGetValue(runAttrs, kCTFontAttributeName); - BOOL isColorGlyph = ASTextCTFontContainsColorBitmapGlyphs(font); - - NSUInteger prevIdx = 0; - ASTextRunGlyphDrawMode prevMode = ASTextRunGlyphDrawModeHorizontal; - NSString *layoutStr = layout.text.string; - for (NSUInteger g = 0; g < glyphCount; g++) { - BOOL glyphRotate = 0, glyphRotateMove = NO; - CFIndex runStrLen = runStrIdx[g + 1] - runStrIdx[g]; - if (isColorGlyph) { - glyphRotate = YES; - } else if (runStrLen == 1) { - unichar c = [layoutStr characterAtIndex:runStrIdx[g]]; - glyphRotate = [rotateCharset characterIsMember:c]; - if (glyphRotate) glyphRotateMove = [rotateMoveCharset characterIsMember:c]; - } else if (runStrLen > 1){ - NSString *glyphStr = [layoutStr substringWithRange:NSMakeRange(runStrIdx[g], runStrLen)]; - BOOL glyphRotate = [glyphStr rangeOfCharacterFromSet:rotateCharset].location != NSNotFound; - if (glyphRotate) glyphRotateMove = [glyphStr rangeOfCharacterFromSet:rotateMoveCharset].location != NSNotFound; - } - - ASTextRunGlyphDrawMode mode = glyphRotateMove ? ASTextRunGlyphDrawModeVerticalRotateMove : (glyphRotate ? ASTextRunGlyphDrawModeVerticalRotate : ASTextRunGlyphDrawModeHorizontal); - if (g == 0) { - prevMode = mode; - } else if (mode != prevMode) { - ASTextRunGlyphRange *aRange = [ASTextRunGlyphRange rangeWithRange:NSMakeRange(prevIdx, g - prevIdx) drawMode:prevMode]; - [runRanges addObject:aRange]; - prevIdx = g; - prevMode = mode; - } - } - if (prevIdx < glyphCount) { - ASTextRunGlyphRange *aRange = [ASTextRunGlyphRange rangeWithRange:NSMakeRange(prevIdx, glyphCount - prevIdx) drawMode:prevMode]; - [runRanges addObject:aRange]; - } - - } - }; - for (ASTextLine *line in lines) { - lineBlock(line); - } - if (truncatedLine) lineBlock(truncatedLine); - } - - if (visibleRange.length > 0) { - layout.needDrawText = YES; - - void (^block)(NSDictionary *attrs, NSRange range, BOOL *stop) = ^(NSDictionary *attrs, NSRange range, BOOL *stop) { - if (attrs[ASTextHighlightAttributeName]) layout.containsHighlight = YES; - if (attrs[ASTextBlockBorderAttributeName]) layout.needDrawBlockBorder = YES; - if (attrs[ASTextBackgroundBorderAttributeName]) layout.needDrawBackgroundBorder = YES; - if (attrs[ASTextShadowAttributeName] || attrs[NSShadowAttributeName]) layout.needDrawShadow = YES; - if (attrs[ASTextUnderlineAttributeName]) layout.needDrawUnderline = YES; - if (attrs[ASTextAttachmentAttributeName]) layout.needDrawAttachment = YES; - if (attrs[ASTextInnerShadowAttributeName]) layout.needDrawInnerShadow = YES; - if (attrs[ASTextStrikethroughAttributeName]) layout.needDrawStrikethrough = YES; - if (attrs[ASTextBorderAttributeName]) layout.needDrawBorder = YES; - }; - - [layout.text enumerateAttributesInRange:visibleRange options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:block]; - if (truncatedLine) { - [truncationToken enumerateAttributesInRange:NSMakeRange(0, truncationToken.length) options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:block]; - } - } - - attachments = [NSMutableArray new]; - attachmentRanges = [NSMutableArray new]; - attachmentRects = [NSMutableArray new]; - attachmentContentsSet = [NSMutableSet new]; - for (NSUInteger i = 0, max = lines.count; i < max; i++) { - ASTextLine *line = lines[i]; - if (truncatedLine && line.index == truncatedLine.index) line = truncatedLine; - if (line.attachments.count > 0) { - [attachments addObjectsFromArray:line.attachments]; - [attachmentRanges addObjectsFromArray:line.attachmentRanges]; - [attachmentRects addObjectsFromArray:line.attachmentRects]; - for (ASTextAttachment *attachment in line.attachments) { - if (attachment.content) { - [attachmentContentsSet addObject:attachment.content]; - } - } - } - } - if (attachments.count == 0) { - attachments = attachmentRanges = attachmentRects = nil; - } - - layout.frame = ctFrame; - layout.lines = lines; - layout.truncatedLine = truncatedLine; - layout.attachments = attachments; - layout.attachmentRanges = attachmentRanges; - layout.attachmentRects = attachmentRects; - layout.attachmentContentsSet = attachmentContentsSet; - layout.rowCount = rowCount; - layout.visibleRange = visibleRange; - layout.textBoundingRect = textBoundingRect; - layout.textBoundingSize = textBoundingSize; - layout.lineRowsEdge = lineRowsEdge; - layout.lineRowsIndex = lineRowsIndex; - CFRelease(cgPath); - CFRelease(ctSetter); - CFRelease(ctFrame); - if (lineOrigins) free(lineOrigins); - return layout; -} - -+ (NSArray *)layoutWithContainers:(NSArray *)containers text:(NSAttributedString *)text { - return [self layoutWithContainers:containers text:text range:NSMakeRange(0, text.length)]; -} - -+ (NSArray *)layoutWithContainers:(NSArray *)containers text:(NSAttributedString *)text range:(NSRange)range { - if (!containers || !text) return nil; - if (range.location + range.length > text.length) return nil; - NSMutableArray *layouts = [[NSMutableArray alloc] init]; - for (NSUInteger i = 0, max = containers.count; i < max; i++) { - ASTextContainer *container = containers[i]; - ASTextLayout *layout = [self layoutWithContainer:container text:text range:range]; - if (!layout) return nil; - NSInteger length = (NSInteger)range.length - (NSInteger)layout.visibleRange.length; - if (length <= 0) { - range.length = 0; - range.location = text.length; - } else { - range.length = length; - range.location += layout.visibleRange.length; - } - } - return layouts; -} - -- (void)setFrame:(CTFrameRef)frame { - if (_frame != frame) { - if (frame) CFRetain(frame); - if (_frame) CFRelease(_frame); - _frame = frame; - } -} - -- (void)dealloc { - if (_frame) CFRelease(_frame); - if (_lineRowsIndex) free(_lineRowsIndex); - if (_lineRowsEdge) free(_lineRowsEdge); -} - -#pragma mark - Copying - -- (id)copyWithZone:(NSZone *)zone { - return self; // readonly object -} - - -#pragma mark - Query - -/** - Get the row index with 'edge' distance. - - @param edge The distance from edge to the point. - If vertical form, the edge is left edge, otherwise the edge is top edge. - - @return Returns NSNotFound if there's no row at the point. - */ -- (NSUInteger)_rowIndexForEdge:(CGFloat)edge { - if (_rowCount == 0) return NSNotFound; - BOOL isVertical = _container.verticalForm; - NSUInteger lo = 0, hi = _rowCount - 1, mid = 0; - NSUInteger rowIdx = NSNotFound; - while (lo <= hi) { - mid = (lo + hi) / 2; - ASRowEdge oneEdge = _lineRowsEdge[mid]; - if (isVertical ? - (oneEdge.foot <= edge && edge <= oneEdge.head) : - (oneEdge.head <= edge && edge <= oneEdge.foot)) { - rowIdx = mid; - break; - } - if ((isVertical ? (edge > oneEdge.head) : (edge < oneEdge.head))) { - if (mid == 0) break; - hi = mid - 1; - } else { - lo = mid + 1; - } - } - return rowIdx; -} - -/** - Get the closest row index with 'edge' distance. - - @param edge The distance from edge to the point. - If vertical form, the edge is left edge, otherwise the edge is top edge. - - @return Returns NSNotFound if there's no line. - */ -- (NSUInteger)_closestRowIndexForEdge:(CGFloat)edge { - if (_rowCount == 0) return NSNotFound; - NSUInteger rowIdx = [self _rowIndexForEdge:edge]; - if (rowIdx == NSNotFound) { - if (_container.verticalForm) { - if (edge > _lineRowsEdge[0].head) { - rowIdx = 0; - } else if (edge < _lineRowsEdge[_rowCount - 1].foot) { - rowIdx = _rowCount - 1; - } - } else { - if (edge < _lineRowsEdge[0].head) { - rowIdx = 0; - } else if (edge > _lineRowsEdge[_rowCount - 1].foot) { - rowIdx = _rowCount - 1; - } - } - } - return rowIdx; -} - -/** - Get a CTRun from a line position. - - @param line The text line. - @param position The position in the whole text. - - @return Returns NULL if not found (no CTRun at the position). - */ -- (CTRunRef)_runForLine:(ASTextLine *)line position:(ASTextPosition *)position { - if (!line || !position) return NULL; - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSUInteger i = 0, max = CFArrayGetCount(runs); i < max; i++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, i); - CFRange range = CTRunGetStringRange(run); - if (position.affinity == ASTextAffinityBackward) { - if (range.location < position.offset && position.offset <= range.location + range.length) { - return run; - } - } else { - if (range.location <= position.offset && position.offset < range.location + range.length) { - return run; - } - } - } - return NULL; -} - -/** - Whether the position is inside a composed character sequence. - - @param line The text line. - @param position Text text position in whole text. - @param block The block to be executed before returns YES. - left: left X offset - right: right X offset - prev: left position - next: right position - */ -- (BOOL)_insideComposedCharacterSequences:(ASTextLine *)line position:(NSUInteger)position block:(void (^)(CGFloat left, CGFloat right, NSUInteger prev, NSUInteger next))block { - NSRange range = line.range; - if (range.length == 0) return NO; - __block BOOL inside = NO; - __block NSUInteger _prev, _next; - [_text.string enumerateSubstringsInRange:range options:NSStringEnumerationByComposedCharacterSequences usingBlock: ^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) { - NSUInteger prev = substringRange.location; - NSUInteger next = substringRange.location + substringRange.length; - if (prev == position || next == position) { - *stop = YES; - } - if (prev < position && position < next) { - inside = YES; - _prev = prev; - _next = next; - *stop = YES; - } - }]; - if (inside && block) { - CGFloat left = [self offsetForTextPosition:_prev lineIndex:line.index]; - CGFloat right = [self offsetForTextPosition:_next lineIndex:line.index]; - block(left, right, _prev, _next); - } - return inside; -} - -/** - Whether the position is inside an emoji (such as National Flag Emoji). - - @param line The text line. - @param position Text text position in whole text. - @param block Yhe block to be executed before returns YES. - left: emoji's left X offset - right: emoji's right X offset - prev: emoji's left position - next: emoji's right position - */ -- (BOOL)_insideEmoji:(ASTextLine *)line position:(NSUInteger)position block:(void (^)(CGFloat left, CGFloat right, NSUInteger prev, NSUInteger next))block { - if (!line) return NO; - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSUInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - NSUInteger glyphCount = CTRunGetGlyphCount(run); - if (glyphCount == 0) continue; - CFRange range = CTRunGetStringRange(run); - if (range.length <= 1) continue; - if (position <= range.location || position >= range.location + range.length) continue; - CFDictionaryRef attrs = CTRunGetAttributes(run); - CTFontRef font = (CTFontRef)CFDictionaryGetValue(attrs, kCTFontAttributeName); - if (!ASTextCTFontContainsColorBitmapGlyphs(font)) continue; - - // Here's Emoji runs (larger than 1 unichar), and position is inside the range. - CFIndex indices[glyphCount]; - CTRunGetStringIndices(run, CFRangeMake(0, glyphCount), indices); - for (NSUInteger g = 0; g < glyphCount; g++) { - CFIndex prev = indices[g]; - CFIndex next = g + 1 < glyphCount ? indices[g + 1] : range.location + range.length; - if (position == prev) break; // Emoji edge - if (prev < position && position < next) { // inside an emoji (such as National Flag Emoji) - CGPoint pos = CGPointZero; - CGSize adv = CGSizeZero; - CTRunGetPositions(run, CFRangeMake(g, 1), &pos); - CTRunGetAdvances(run, CFRangeMake(g, 1), &adv); - if (block) { - block(line.position.x + pos.x, - line.position.x + pos.x + adv.width, - prev, next); - } - return YES; - } - } - } - return NO; -} -/** - Whether the write direction is RTL at the specified point - - @param line The text line - @param point The point in layout. - - @return YES if RTL. - */ -- (BOOL)_isRightToLeftInLine:(ASTextLine *)line atPoint:(CGPoint)point { - if (!line) return NO; - // get write direction - BOOL RTL = NO; - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSUInteger r = 0, max = CFArrayGetCount(runs); r < max; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - CGPoint glyphPosition; - CTRunGetPositions(run, CFRangeMake(0, 1), &glyphPosition); - if (_container.verticalForm) { - CGFloat runX = glyphPosition.x; - runX += line.position.y; - CGFloat runWidth = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), NULL, NULL, NULL); - if (runX <= point.y && point.y <= runX + runWidth) { - if (CTRunGetStatus(run) & kCTRunStatusRightToLeft) RTL = YES; - break; - } - } else { - CGFloat runX = glyphPosition.x; - runX += line.position.x; - CGFloat runWidth = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), NULL, NULL, NULL); - if (runX <= point.x && point.x <= runX + runWidth) { - if (CTRunGetStatus(run) & kCTRunStatusRightToLeft) RTL = YES; - break; - } - } - } - return RTL; -} - -/** - Correct the range's edge. - */ -- (ASTextRange *)_correctedRangeWithEdge:(ASTextRange *)range { - NSRange visibleRange = self.visibleRange; - ASTextPosition *start = range.start; - ASTextPosition *end = range.end; - - if (start.offset == visibleRange.location && start.affinity == ASTextAffinityBackward) { - start = [ASTextPosition positionWithOffset:start.offset affinity:ASTextAffinityForward]; - } - - if (end.offset == visibleRange.location + visibleRange.length && start.affinity == ASTextAffinityForward) { - end = [ASTextPosition positionWithOffset:end.offset affinity:ASTextAffinityBackward]; - } - - if (start != range.start || end != range.end) { - range = [ASTextRange rangeWithStart:start end:end]; - } - return range; -} - -- (NSUInteger)lineIndexForRow:(NSUInteger)row { - if (row >= _rowCount) return NSNotFound; - return _lineRowsIndex[row]; -} - -- (NSUInteger)lineCountForRow:(NSUInteger)row { - if (row >= _rowCount) return NSNotFound; - if (row == _rowCount - 1) { - return _lines.count - _lineRowsIndex[row]; - } else { - return _lineRowsIndex[row + 1] - _lineRowsIndex[row]; - } -} - -- (NSUInteger)rowIndexForLine:(NSUInteger)line { - if (line >= _lines.count) return NSNotFound; - return ((ASTextLine *)_lines[line]).row; -} - -- (NSUInteger)lineIndexForPoint:(CGPoint)point { - if (_lines.count == 0 || _rowCount == 0) return NSNotFound; - NSUInteger rowIdx = [self _rowIndexForEdge:_container.verticalForm ? point.x : point.y]; - if (rowIdx == NSNotFound) return NSNotFound; - - NSUInteger lineIdx0 = _lineRowsIndex[rowIdx]; - NSUInteger lineIdx1 = rowIdx == _rowCount - 1 ? _lines.count - 1 : _lineRowsIndex[rowIdx + 1] - 1; - for (NSUInteger i = lineIdx0; i <= lineIdx1; i++) { - CGRect bounds = ((ASTextLine *)_lines[i]).bounds; - if (CGRectContainsPoint(bounds, point)) return i; - } - - return NSNotFound; -} - -- (NSUInteger)closestLineIndexForPoint:(CGPoint)point { - BOOL isVertical = _container.verticalForm; - if (_lines.count == 0 || _rowCount == 0) return NSNotFound; - NSUInteger rowIdx = [self _closestRowIndexForEdge:isVertical ? point.x : point.y]; - if (rowIdx == NSNotFound) return NSNotFound; - - NSUInteger lineIdx0 = _lineRowsIndex[rowIdx]; - NSUInteger lineIdx1 = rowIdx == _rowCount - 1 ? _lines.count - 1 : _lineRowsIndex[rowIdx + 1] - 1; - if (lineIdx0 == lineIdx1) return lineIdx0; - - CGFloat minDistance = CGFLOAT_MAX; - NSUInteger minIndex = lineIdx0; - for (NSUInteger i = lineIdx0; i <= lineIdx1; i++) { - CGRect bounds = ((ASTextLine *)_lines[i]).bounds; - if (isVertical) { - if (bounds.origin.y <= point.y && point.y <= bounds.origin.y + bounds.size.height) return i; - CGFloat distance; - if (point.y < bounds.origin.y) { - distance = bounds.origin.y - point.y; - } else { - distance = point.y - (bounds.origin.y + bounds.size.height); - } - if (distance < minDistance) { - minDistance = distance; - minIndex = i; - } - } else { - if (bounds.origin.x <= point.x && point.x <= bounds.origin.x + bounds.size.width) return i; - CGFloat distance; - if (point.x < bounds.origin.x) { - distance = bounds.origin.x - point.x; - } else { - distance = point.x - (bounds.origin.x + bounds.size.width); - } - if (distance < minDistance) { - minDistance = distance; - minIndex = i; - } - } - } - return minIndex; -} - -- (CGFloat)offsetForTextPosition:(NSUInteger)position lineIndex:(NSUInteger)lineIndex { - if (lineIndex >= _lines.count) return CGFLOAT_MAX; - ASTextLine *line = _lines[lineIndex]; - CFRange range = CTLineGetStringRange(line.CTLine); - if (position < range.location || position > range.location + range.length) return CGFLOAT_MAX; - - CGFloat offset = CTLineGetOffsetForStringIndex(line.CTLine, position, NULL); - return _container.verticalForm ? (offset + line.position.y) : (offset + line.position.x); -} - -- (NSUInteger)textPositionForPoint:(CGPoint)point lineIndex:(NSUInteger)lineIndex { - if (lineIndex >= _lines.count) return NSNotFound; - ASTextLine *line = _lines[lineIndex]; - if (_container.verticalForm) { - point.x = point.y - line.position.y; - point.y = 0; - } else { - point.x -= line.position.x; - point.y = 0; - } - CFIndex idx = CTLineGetStringIndexForPosition(line.CTLine, point); - if (idx == kCFNotFound) return NSNotFound; - - /* - If the emoji contains one or more variant form (such as ☔️ "\u2614\uFE0F") - and the font size is smaller than 379/15, then each variant form ("\uFE0F") - will rendered as a single blank glyph behind the emoji glyph. Maybe it's a - bug in CoreText? Seems iOS8.3 fixes this problem. - - If the point hit the blank glyph, the CTLineGetStringIndexForPosition() - returns the position before the emoji glyph, but it should returns the - position after the emoji and variant form. - - Here's a workaround. - */ - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSUInteger r = 0, max = CFArrayGetCount(runs); r < max; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - CFRange range = CTRunGetStringRange(run); - if (range.location <= idx && idx < range.location + range.length) { - NSUInteger glyphCount = CTRunGetGlyphCount(run); - if (glyphCount == 0) break; - CFDictionaryRef attrs = CTRunGetAttributes(run); - CTFontRef font = (CTFontRef)CFDictionaryGetValue(attrs, kCTFontAttributeName); - if (!ASTextCTFontContainsColorBitmapGlyphs(font)) break; - - CFIndex indices[glyphCount]; - CGPoint positions[glyphCount]; - CTRunGetStringIndices(run, CFRangeMake(0, glyphCount), indices); - CTRunGetPositions(run, CFRangeMake(0, glyphCount), positions); - for (NSUInteger g = 0; g < glyphCount; g++) { - NSUInteger gIdx = indices[g]; - if (gIdx == idx && g + 1 < glyphCount) { - CGFloat right = positions[g + 1].x; - if (point.x < right) break; - NSUInteger next = indices[g + 1]; - do { - if (next == range.location + range.length) break; - unichar c = [_text.string characterAtIndex:next]; - if ((c == 0xFE0E || c == 0xFE0F)) { // unicode variant form for emoji style - next++; - } else break; - } - while (1); - if (next != indices[g + 1]) idx = next; - break; - } - } - break; - } - } - return idx; -} - -- (ASTextPosition *)closestPositionToPoint:(CGPoint)point { - BOOL isVertical = _container.verticalForm; - // When call CTLineGetStringIndexForPosition() on ligature such as 'fi', - // and the point `hit` the glyph's left edge, it may get the ligature inside offset. - // I don't know why, maybe it's a bug of CoreText. Try to avoid it. - if (isVertical) point.y += 0.00001234; - else point.x += 0.00001234; - - NSUInteger lineIndex = [self closestLineIndexForPoint:point]; - if (lineIndex == NSNotFound) return nil; - ASTextLine *line = _lines[lineIndex]; - __block NSUInteger position = [self textPositionForPoint:point lineIndex:lineIndex]; - if (position == NSNotFound) position = line.range.location; - if (position <= _visibleRange.location) { - return [ASTextPosition positionWithOffset:_visibleRange.location affinity:ASTextAffinityForward]; - } else if (position >= _visibleRange.location + _visibleRange.length) { - return [ASTextPosition positionWithOffset:_visibleRange.location + _visibleRange.length affinity:ASTextAffinityBackward]; - } - - ASTextAffinity finalAffinity = ASTextAffinityForward; - BOOL finalAffinityDetected = NO; - - // binding range - NSRange bindingRange; - ASTextBinding *binding = [_text attribute:ASTextBindingAttributeName atIndex:position longestEffectiveRange:&bindingRange inRange:NSMakeRange(0, _text.length)]; - if (binding && bindingRange.length > 0) { - NSUInteger headLineIdx = [self lineIndexForPosition:[ASTextPosition positionWithOffset:bindingRange.location]]; - NSUInteger tailLineIdx = [self lineIndexForPosition:[ASTextPosition positionWithOffset:bindingRange.location + bindingRange.length affinity:ASTextAffinityBackward]]; - if (headLineIdx == lineIndex && lineIndex == tailLineIdx) { // all in same line - CGFloat left = [self offsetForTextPosition:bindingRange.location lineIndex:lineIndex]; - CGFloat right = [self offsetForTextPosition:bindingRange.location + bindingRange.length lineIndex:lineIndex]; - if (left != CGFLOAT_MAX && right != CGFLOAT_MAX) { - if (_container.isVerticalForm) { - if (fabs(point.y - left) < fabs(point.y - right)) { - position = bindingRange.location; - finalAffinity = ASTextAffinityForward; - } else { - position = bindingRange.location + bindingRange.length; - finalAffinity = ASTextAffinityBackward; - } - } else { - if (fabs(point.x - left) < fabs(point.x - right)) { - position = bindingRange.location; - finalAffinity = ASTextAffinityForward; - } else { - position = bindingRange.location + bindingRange.length; - finalAffinity = ASTextAffinityBackward; - } - } - } else if (left != CGFLOAT_MAX) { - position = left; - finalAffinity = ASTextAffinityForward; - } else if (right != CGFLOAT_MAX) { - position = right; - finalAffinity = ASTextAffinityBackward; - } - finalAffinityDetected = YES; - } else if (headLineIdx == lineIndex) { - CGFloat left = [self offsetForTextPosition:bindingRange.location lineIndex:lineIndex]; - if (left != CGFLOAT_MAX) { - position = bindingRange.location; - finalAffinity = ASTextAffinityForward; - finalAffinityDetected = YES; - } - } else if (tailLineIdx == lineIndex) { - CGFloat right = [self offsetForTextPosition:bindingRange.location + bindingRange.length lineIndex:lineIndex]; - if (right != CGFLOAT_MAX) { - position = bindingRange.location + bindingRange.length; - finalAffinity = ASTextAffinityBackward; - finalAffinityDetected = YES; - } - } else { - BOOL onLeft = NO, onRight = NO; - if (headLineIdx != NSNotFound && tailLineIdx != NSNotFound) { - if (abs((int)headLineIdx - (int)lineIndex) < abs((int)tailLineIdx - (int)lineIndex)) onLeft = YES; - else onRight = YES; - } else if (headLineIdx != NSNotFound) { - onLeft = YES; - } else if (tailLineIdx != NSNotFound) { - onRight = YES; - } - - if (onLeft) { - CGFloat left = [self offsetForTextPosition:bindingRange.location lineIndex:headLineIdx]; - if (left != CGFLOAT_MAX) { - lineIndex = headLineIdx; - line = _lines[headLineIdx]; - position = bindingRange.location; - finalAffinity = ASTextAffinityForward; - finalAffinityDetected = YES; - } - } else if (onRight) { - CGFloat right = [self offsetForTextPosition:bindingRange.location + bindingRange.length lineIndex:tailLineIdx]; - if (right != CGFLOAT_MAX) { - lineIndex = tailLineIdx; - line = _lines[tailLineIdx]; - position = bindingRange.location + bindingRange.length; - finalAffinity = ASTextAffinityBackward; - finalAffinityDetected = YES; - } - } - } - } - - // empty line - if (line.range.length == 0) { - BOOL behind = (_lines.count > 1 && lineIndex == _lines.count - 1); //end line - return [ASTextPosition positionWithOffset:line.range.location affinity:behind ? ASTextAffinityBackward:ASTextAffinityForward]; - } - - // detect weather the line is a linebreak token - if (line.range.length <= 2) { - NSString *str = [_text.string substringWithRange:line.range]; - if (ASTextIsLinebreakString(str)) { // an empty line ("\r", "\n", "\r\n") - return [ASTextPosition positionWithOffset:line.range.location]; - } - } - - // above whole text frame - if (lineIndex == 0 && (isVertical ? (point.x > line.right) : (point.y < line.top))) { - position = 0; - finalAffinity = ASTextAffinityForward; - finalAffinityDetected = YES; - } - // below whole text frame - if (lineIndex == _lines.count - 1 && (isVertical ? (point.x < line.left) : (point.y > line.bottom))) { - position = line.range.location + line.range.length; - finalAffinity = ASTextAffinityBackward; - finalAffinityDetected = YES; - } - - // There must be at least one non-linebreak char, - // ignore the linebreak characters at line end if exists. - if (position >= line.range.location + line.range.length - 1) { - if (position > line.range.location) { - unichar c1 = [_text.string characterAtIndex:position - 1]; - if (ASTextIsLinebreakChar(c1)) { - position--; - if (position > line.range.location) { - unichar c0 = [_text.string characterAtIndex:position - 1]; - if (ASTextIsLinebreakChar(c0)) { - position--; - } - } - } - } - } - if (position == line.range.location) { - return [ASTextPosition positionWithOffset:position]; - } - if (position == line.range.location + line.range.length) { - return [ASTextPosition positionWithOffset:position affinity:ASTextAffinityBackward]; - } - - [self _insideComposedCharacterSequences:line position:position block: ^(CGFloat left, CGFloat right, NSUInteger prev, NSUInteger next) { - if (isVertical) { - position = fabs(left - point.y) < fabs(right - point.y) < (right ? prev : next); - } else { - position = fabs(left - point.x) < fabs(right - point.x) < (right ? prev : next); - } - }]; - - [self _insideEmoji:line position:position block: ^(CGFloat left, CGFloat right, NSUInteger prev, NSUInteger next) { - if (isVertical) { - position = fabs(left - point.y) < fabs(right - point.y) < (right ? prev : next); - } else { - position = fabs(left - point.x) < fabs(right - point.x) < (right ? prev : next); - } - }]; - - if (position < _visibleRange.location) position = _visibleRange.location; - else if (position > _visibleRange.location + _visibleRange.length) position = _visibleRange.location + _visibleRange.length; - - if (!finalAffinityDetected) { - CGFloat ofs = [self offsetForTextPosition:position lineIndex:lineIndex]; - if (ofs != CGFLOAT_MAX) { - BOOL RTL = [self _isRightToLeftInLine:line atPoint:point]; - if (position >= line.range.location + line.range.length) { - finalAffinity = RTL ? ASTextAffinityForward : ASTextAffinityBackward; - } else if (position <= line.range.location) { - finalAffinity = RTL ? ASTextAffinityBackward : ASTextAffinityForward; - } else { - finalAffinity = (ofs < (isVertical ? point.y : point.x) && !RTL) ? ASTextAffinityForward : ASTextAffinityBackward; - } - } - } - - return [ASTextPosition positionWithOffset:position affinity:finalAffinity]; -} - -- (ASTextPosition *)positionForPoint:(CGPoint)point - oldPosition:(ASTextPosition *)oldPosition - otherPosition:(ASTextPosition *)otherPosition { - if (!oldPosition || !otherPosition) { - return oldPosition; - } - ASTextPosition *newPos = [self closestPositionToPoint:point]; - if (!newPos) return oldPosition; - if ([newPos compare:otherPosition] == [oldPosition compare:otherPosition] && - newPos.offset != otherPosition.offset) { - return newPos; - } - NSUInteger lineIndex = [self lineIndexForPosition:otherPosition]; - if (lineIndex == NSNotFound) return oldPosition; - ASTextLine *line = _lines[lineIndex]; - ASRowEdge vertical = _lineRowsEdge[line.row]; - if (_container.verticalForm) { - point.x = (vertical.head + vertical.foot) * 0.5; - } else { - point.y = (vertical.head + vertical.foot) * 0.5; - } - newPos = [self closestPositionToPoint:point]; - if ([newPos compare:otherPosition] == [oldPosition compare:otherPosition] && - newPos.offset != otherPosition.offset) { - return newPos; - } - - if (_container.isVerticalForm) { - if ([oldPosition compare:otherPosition] == NSOrderedAscending) { // search backward - ASTextRange *range = [self textRangeByExtendingPosition:otherPosition inDirection:UITextLayoutDirectionUp offset:1]; - if (range) return range.start; - } else { // search forward - ASTextRange *range = [self textRangeByExtendingPosition:otherPosition inDirection:UITextLayoutDirectionDown offset:1]; - if (range) return range.end; - } - } else { - if ([oldPosition compare:otherPosition] == NSOrderedAscending) { // search backward - ASTextRange *range = [self textRangeByExtendingPosition:otherPosition inDirection:UITextLayoutDirectionLeft offset:1]; - if (range) return range.start; - } else { // search forward - ASTextRange *range = [self textRangeByExtendingPosition:otherPosition inDirection:UITextLayoutDirectionRight offset:1]; - if (range) return range.end; - } - } - - return oldPosition; -} - -- (ASTextRange *)textRangeAtPoint:(CGPoint)point { - NSUInteger lineIndex = [self lineIndexForPoint:point]; - if (lineIndex == NSNotFound) return nil; - NSUInteger textPosition = [self textPositionForPoint:point lineIndex:[self lineIndexForPoint:point]]; - if (textPosition == NSNotFound) return nil; - ASTextPosition *pos = [self closestPositionToPoint:point]; - if (!pos) return nil; - - // get write direction - BOOL RTL = [self _isRightToLeftInLine:_lines[lineIndex] atPoint:point]; - CGRect rect = [self caretRectForPosition:pos]; - if (CGRectIsNull(rect)) return nil; - - if (_container.verticalForm) { - ASTextRange *range = [self textRangeByExtendingPosition:pos inDirection:(rect.origin.y >= point.y && !RTL) ? UITextLayoutDirectionUp:UITextLayoutDirectionDown offset:1]; - return range; - } else { - ASTextRange *range = [self textRangeByExtendingPosition:pos inDirection:(rect.origin.x >= point.x && !RTL) ? UITextLayoutDirectionLeft:UITextLayoutDirectionRight offset:1]; - return range; - } -} - -- (ASTextRange *)closestTextRangeAtPoint:(CGPoint)point { - ASTextPosition *pos = [self closestPositionToPoint:point]; - if (!pos) return nil; - NSUInteger lineIndex = [self lineIndexForPosition:pos]; - if (lineIndex == NSNotFound) return nil; - ASTextLine *line = _lines[lineIndex]; - BOOL RTL = [self _isRightToLeftInLine:line atPoint:point]; - CGRect rect = [self caretRectForPosition:pos]; - if (CGRectIsNull(rect)) return nil; - - UITextLayoutDirection direction = UITextLayoutDirectionRight; - if (pos.offset >= line.range.location + line.range.length) { - if (direction != RTL) { - direction = _container.verticalForm ? UITextLayoutDirectionUp : UITextLayoutDirectionLeft; - } else { - direction = _container.verticalForm ? UITextLayoutDirectionDown : UITextLayoutDirectionRight; - } - } else if (pos.offset <= line.range.location) { - if (direction != RTL) { - direction = _container.verticalForm ? UITextLayoutDirectionDown : UITextLayoutDirectionRight; - } else { - direction = _container.verticalForm ? UITextLayoutDirectionUp : UITextLayoutDirectionLeft; - } - } else { - if (_container.verticalForm) { - direction = (rect.origin.y >= point.y && !RTL) ? UITextLayoutDirectionUp:UITextLayoutDirectionDown; - } else { - direction = (rect.origin.x >= point.x && !RTL) ? UITextLayoutDirectionLeft:UITextLayoutDirectionRight; - } - } - - ASTextRange *range = [self textRangeByExtendingPosition:pos inDirection:direction offset:1]; - return range; -} - -- (ASTextRange *)textRangeByExtendingPosition:(ASTextPosition *)position { - NSUInteger visibleStart = _visibleRange.location; - NSUInteger visibleEnd = _visibleRange.location + _visibleRange.length; - - if (!position) return nil; - if (position.offset < visibleStart || position.offset > visibleEnd) return nil; - - // head or tail, returns immediately - if (position.offset == visibleStart) { - return [ASTextRange rangeWithRange:NSMakeRange(position.offset, 0)]; - } else if (position.offset == visibleEnd) { - return [ASTextRange rangeWithRange:NSMakeRange(position.offset, 0) affinity:ASTextAffinityBackward]; - } - - // binding range - NSRange tRange; - ASTextBinding *binding = [_text attribute:ASTextBindingAttributeName atIndex:position.offset longestEffectiveRange:&tRange inRange:_visibleRange]; - if (binding && tRange.length > 0 && tRange.location < position.offset) { - return [ASTextRange rangeWithRange:tRange]; - } - - // inside emoji or composed character sequences - NSUInteger lineIndex = [self lineIndexForPosition:position]; - if (lineIndex != NSNotFound) { - __block NSUInteger _prev, _next; - BOOL emoji = NO, seq = NO; - - ASTextLine *line = _lines[lineIndex]; - emoji = [self _insideEmoji:line position:position.offset block: ^(CGFloat left, CGFloat right, NSUInteger prev, NSUInteger next) { - _prev = prev; - _next = next; - }]; - if (!emoji) { - seq = [self _insideComposedCharacterSequences:line position:position.offset block: ^(CGFloat left, CGFloat right, NSUInteger prev, NSUInteger next) { - _prev = prev; - _next = next; - }]; - } - if (emoji || seq) { - return [ASTextRange rangeWithRange:NSMakeRange(_prev, _next - _prev)]; - } - } - - // inside linebreak '\r\n' - if (position.offset > visibleStart && position.offset < visibleEnd) { - unichar c0 = [_text.string characterAtIndex:position.offset - 1]; - if ((c0 == '\r') && position.offset < visibleEnd) { - unichar c1 = [_text.string characterAtIndex:position.offset]; - if (c1 == '\n') { - return [ASTextRange rangeWithStart:[ASTextPosition positionWithOffset:position.offset - 1] end:[ASTextPosition positionWithOffset:position.offset + 1]]; - } - } - if (ASTextIsLinebreakChar(c0) && position.affinity == ASTextAffinityBackward) { - NSString *str = [_text.string substringToIndex:position.offset]; - NSUInteger len = ASTextLinebreakTailLength(str); - return [ASTextRange rangeWithStart:[ASTextPosition positionWithOffset:position.offset - len] end:[ASTextPosition positionWithOffset:position.offset]]; - } - } - - return [ASTextRange rangeWithRange:NSMakeRange(position.offset, 0) affinity:position.affinity]; -} - -- (ASTextRange *)textRangeByExtendingPosition:(ASTextPosition *)position - inDirection:(UITextLayoutDirection)direction - offset:(NSInteger)offset { - NSInteger visibleStart = _visibleRange.location; - NSInteger visibleEnd = _visibleRange.location + _visibleRange.length; - - if (!position) return nil; - if (position.offset < visibleStart || position.offset > visibleEnd) return nil; - if (offset == 0) return [self textRangeByExtendingPosition:position]; - - BOOL isVerticalForm = _container.verticalForm; - BOOL verticalMove, forwardMove; - - if (isVerticalForm) { - verticalMove = direction == UITextLayoutDirectionLeft || direction == UITextLayoutDirectionRight; - forwardMove = direction == UITextLayoutDirectionLeft || direction == UITextLayoutDirectionDown; - } else { - verticalMove = direction == UITextLayoutDirectionUp || direction == UITextLayoutDirectionDown; - forwardMove = direction == UITextLayoutDirectionDown || direction == UITextLayoutDirectionRight; - } - - if (offset < 0) { - forwardMove = !forwardMove; - offset = -offset; - } - - // head or tail, returns immediately - if (!forwardMove && position.offset == visibleStart) { - return [ASTextRange rangeWithRange:NSMakeRange(_visibleRange.location, 0)]; - } else if (forwardMove && position.offset == visibleEnd) { - return [ASTextRange rangeWithRange:NSMakeRange(position.offset, 0) affinity:ASTextAffinityBackward]; - } - - // extend from position - ASTextRange *fromRange = [self textRangeByExtendingPosition:position]; - if (!fromRange) return nil; - ASTextRange *allForward = [ASTextRange rangeWithStart:fromRange.start end:[ASTextPosition positionWithOffset:visibleEnd]]; - ASTextRange *allBackward = [ASTextRange rangeWithStart:[ASTextPosition positionWithOffset:visibleStart] end:fromRange.end]; - - if (verticalMove) { // up/down in text layout - NSInteger lineIndex = [self lineIndexForPosition:position]; - if (lineIndex == NSNotFound) return nil; - - ASTextLine *line = _lines[lineIndex]; - NSInteger moveToRowIndex = (NSInteger)line.row + (forwardMove ? offset : -offset); - if (moveToRowIndex < 0) return allBackward; - else if (moveToRowIndex >= (NSInteger)_rowCount) return allForward; - - CGFloat ofs = [self offsetForTextPosition:position.offset lineIndex:lineIndex]; - if (ofs == CGFLOAT_MAX) return nil; - - NSUInteger moveToLineFirstIndex = [self lineIndexForRow:moveToRowIndex]; - NSUInteger moveToLineCount = [self lineCountForRow:moveToRowIndex]; - if (moveToLineFirstIndex == NSNotFound || moveToLineCount == NSNotFound || moveToLineCount == 0) return nil; - CGFloat mostLeft = CGFLOAT_MAX, mostRight = -CGFLOAT_MAX; - ASTextLine *mostLeftLine = nil, *mostRightLine = nil; - NSUInteger insideIndex = NSNotFound; - for (NSUInteger i = 0; i < moveToLineCount; i++) { - NSUInteger lineIndex = moveToLineFirstIndex + i; - ASTextLine *line = _lines[lineIndex]; - if (isVerticalForm) { - if (line.top <= ofs && ofs <= line.bottom) { - insideIndex = line.index; - break; - } - if (line.top < mostLeft) { - mostLeft = line.top; - mostLeftLine = line; - } - if (line.bottom > mostRight) { - mostRight = line.bottom; - mostRightLine = line; - } - } else { - if (line.left <= ofs && ofs <= line.right) { - insideIndex = line.index; - break; - } - if (line.left < mostLeft) { - mostLeft = line.left; - mostLeftLine = line; - } - if (line.right > mostRight) { - mostRight = line.right; - mostRightLine = line; - } - } - } - BOOL afinityEdge = NO; - if (insideIndex == NSNotFound) { - if (ofs <= mostLeft) { - insideIndex = mostLeftLine.index; - } else { - insideIndex = mostRightLine.index; - } - afinityEdge = YES; - } - ASTextLine *insideLine = _lines[insideIndex]; - NSUInteger pos; - if (isVerticalForm) { - pos = [self textPositionForPoint:CGPointMake(insideLine.position.x, ofs) lineIndex:insideIndex]; - } else { - pos = [self textPositionForPoint:CGPointMake(ofs, insideLine.position.y) lineIndex:insideIndex]; - } - if (pos == NSNotFound) return nil; - ASTextPosition *extPos; - if (afinityEdge) { - if (pos == insideLine.range.location + insideLine.range.length) { - NSString *subStr = [_text.string substringWithRange:insideLine.range]; - NSUInteger lineBreakLen = ASTextLinebreakTailLength(subStr); - extPos = [ASTextPosition positionWithOffset:pos - lineBreakLen]; - } else { - extPos = [ASTextPosition positionWithOffset:pos]; - } - } else { - extPos = [ASTextPosition positionWithOffset:pos]; - } - ASTextRange *ext = [self textRangeByExtendingPosition:extPos]; - if (!ext) return nil; - if (forwardMove) { - return [ASTextRange rangeWithStart:fromRange.start end:ext.end]; - } else { - return [ASTextRange rangeWithStart:ext.start end:fromRange.end]; - } - - } else { // left/right in text layout - ASTextPosition *toPosition = [ASTextPosition positionWithOffset:position.offset + (forwardMove ? offset : -offset)]; - if (toPosition.offset <= visibleStart) return allBackward; - else if (toPosition.offset >= visibleEnd) return allForward; - - ASTextRange *toRange = [self textRangeByExtendingPosition:toPosition]; - if (!toRange) return nil; - - NSInteger start = MIN(fromRange.start.offset, toRange.start.offset); - NSInteger end = MAX(fromRange.end.offset, toRange.end.offset); - return [ASTextRange rangeWithRange:NSMakeRange(start, end - start)]; - } -} - -- (NSUInteger)lineIndexForPosition:(ASTextPosition *)position { - if (!position) return NSNotFound; - if (_lines.count == 0) return NSNotFound; - NSUInteger location = position.offset; - NSInteger lo = 0, hi = _lines.count - 1, mid = 0; - if (position.affinity == ASTextAffinityBackward) { - while (lo <= hi) { - mid = (lo + hi) / 2; - ASTextLine *line = _lines[mid]; - NSRange range = line.range; - if (range.location < location && location <= range.location + range.length) { - return mid; - } - if (location <= range.location) { - hi = mid - 1; - } else { - lo = mid + 1; - } - } - } else { - while (lo <= hi) { - mid = (lo + hi) / 2; - ASTextLine *line = _lines[mid]; - NSRange range = line.range; - if (range.location <= location && location < range.location + range.length) { - return mid; - } - if (location < range.location) { - hi = mid - 1; - } else { - lo = mid + 1; - } - } - } - return NSNotFound; -} - -- (CGPoint)linePositionForPosition:(ASTextPosition *)position { - NSUInteger lineIndex = [self lineIndexForPosition:position]; - if (lineIndex == NSNotFound) return CGPointZero; - ASTextLine *line = _lines[lineIndex]; - CGFloat offset = [self offsetForTextPosition:position.offset lineIndex:lineIndex]; - if (offset == CGFLOAT_MAX) return CGPointZero; - if (_container.verticalForm) { - return CGPointMake(line.position.x, offset); - } else { - return CGPointMake(offset, line.position.y); - } -} - -- (CGRect)caretRectForPosition:(ASTextPosition *)position { - NSUInteger lineIndex = [self lineIndexForPosition:position]; - if (lineIndex == NSNotFound) return CGRectNull; - ASTextLine *line = _lines[lineIndex]; - CGFloat offset = [self offsetForTextPosition:position.offset lineIndex:lineIndex]; - if (offset == CGFLOAT_MAX) return CGRectNull; - if (_container.verticalForm) { - return CGRectMake(line.bounds.origin.x, offset, line.bounds.size.width, 0); - } else { - return CGRectMake(offset, line.bounds.origin.y, 0, line.bounds.size.height); - } -} - -- (CGRect)firstRectForRange:(ASTextRange *)range { - range = [self _correctedRangeWithEdge:range]; - - NSUInteger startLineIndex = [self lineIndexForPosition:range.start]; - NSUInteger endLineIndex = [self lineIndexForPosition:range.end]; - if (startLineIndex == NSNotFound || endLineIndex == NSNotFound) return CGRectNull; - if (startLineIndex > endLineIndex) return CGRectNull; - ASTextLine *startLine = _lines[startLineIndex]; - ASTextLine *endLine = _lines[endLineIndex]; - NSMutableArray *lines = [NSMutableArray new]; - for (NSUInteger i = startLineIndex; i <= startLineIndex; i++) { - ASTextLine *line = _lines[i]; - if (line.row != startLine.row) break; - [lines addObject:line]; - } - if (_container.verticalForm) { - if (lines.count == 1) { - CGFloat top = [self offsetForTextPosition:range.start.offset lineIndex:startLineIndex]; - CGFloat bottom; - if (startLine == endLine) { - bottom = [self offsetForTextPosition:range.end.offset lineIndex:startLineIndex]; - } else { - bottom = startLine.bottom; - } - if (top == CGFLOAT_MAX || bottom == CGFLOAT_MAX) return CGRectNull; - if (top > bottom) ASTEXT_SWAP(top, bottom); - return CGRectMake(startLine.left, top, startLine.width, bottom - top); - } else { - CGFloat top = [self offsetForTextPosition:range.start.offset lineIndex:startLineIndex]; - CGFloat bottom = startLine.bottom; - if (top == CGFLOAT_MAX || bottom == CGFLOAT_MAX) return CGRectNull; - if (top > bottom) ASTEXT_SWAP(top, bottom); - CGRect rect = CGRectMake(startLine.left, top, startLine.width, bottom - top); - for (NSUInteger i = 1; i < lines.count; i++) { - ASTextLine *line = lines[i]; - rect = CGRectUnion(rect, line.bounds); - } - return rect; - } - } else { - if (lines.count == 1) { - CGFloat left = [self offsetForTextPosition:range.start.offset lineIndex:startLineIndex]; - CGFloat right; - if (startLine == endLine) { - right = [self offsetForTextPosition:range.end.offset lineIndex:startLineIndex]; - } else { - right = startLine.right; - } - if (left == CGFLOAT_MAX || right == CGFLOAT_MAX) return CGRectNull; - if (left > right) ASTEXT_SWAP(left, right); - return CGRectMake(left, startLine.top, right - left, startLine.height); - } else { - CGFloat left = [self offsetForTextPosition:range.start.offset lineIndex:startLineIndex]; - CGFloat right = startLine.right; - if (left == CGFLOAT_MAX || right == CGFLOAT_MAX) return CGRectNull; - if (left > right) ASTEXT_SWAP(left, right); - CGRect rect = CGRectMake(left, startLine.top, right - left, startLine.height); - for (NSUInteger i = 1; i < lines.count; i++) { - ASTextLine *line = lines[i]; - rect = CGRectUnion(rect, line.bounds); - } - return rect; - } - } -} - -- (CGRect)rectForRange:(ASTextRange *)range { - NSArray *rects = [self selectionRectsForRange:range]; - if (rects.count == 0) return CGRectNull; - CGRect rectUnion = ((ASTextSelectionRect *)rects.firstObject).rect; - for (NSUInteger i = 1; i < rects.count; i++) { - ASTextSelectionRect *rect = rects[i]; - rectUnion = CGRectUnion(rectUnion, rect.rect); - } - return rectUnion; -} - -- (NSArray *)selectionRectsForRange:(ASTextRange *)range { - range = [self _correctedRangeWithEdge:range]; - - BOOL isVertical = _container.verticalForm; - NSMutableArray *rects = [[NSMutableArray alloc] init]; - if (!range) return rects; - - NSUInteger startLineIndex = [self lineIndexForPosition:range.start]; - NSUInteger endLineIndex = [self lineIndexForPosition:range.end]; - if (startLineIndex == NSNotFound || endLineIndex == NSNotFound) return rects; - if (startLineIndex > endLineIndex) ASTEXT_SWAP(startLineIndex, endLineIndex); - ASTextLine *startLine = _lines[startLineIndex]; - ASTextLine *endLine = _lines[endLineIndex]; - CGFloat offsetStart = [self offsetForTextPosition:range.start.offset lineIndex:startLineIndex]; - CGFloat offsetEnd = [self offsetForTextPosition:range.end.offset lineIndex:endLineIndex]; - - ASTextSelectionRect *start = [ASTextSelectionRect new]; - if (isVertical) { - start.rect = CGRectMake(startLine.left, offsetStart, startLine.width, 0); - } else { - start.rect = CGRectMake(offsetStart, startLine.top, 0, startLine.height); - } - start.containsStart = YES; - start.isVertical = isVertical; - [rects addObject:start]; - - ASTextSelectionRect *end = [ASTextSelectionRect new]; - if (isVertical) { - end.rect = CGRectMake(endLine.left, offsetEnd, endLine.width, 0); - } else { - end.rect = CGRectMake(offsetEnd, endLine.top, 0, endLine.height); - } - end.containsEnd = YES; - end.isVertical = isVertical; - [rects addObject:end]; - - if (startLine.row == endLine.row) { // same row - if (offsetStart > offsetEnd) ASTEXT_SWAP(offsetStart, offsetEnd); - ASTextSelectionRect *rect = [ASTextSelectionRect new]; - if (isVertical) { - rect.rect = CGRectMake(startLine.bounds.origin.x, offsetStart, MAX(startLine.width, endLine.width), offsetEnd - offsetStart); - } else { - rect.rect = CGRectMake(offsetStart, startLine.bounds.origin.y, offsetEnd - offsetStart, MAX(startLine.height, endLine.height)); - } - rect.isVertical = isVertical; - [rects addObject:rect]; - - } else { // more than one row - - // start line select rect - ASTextSelectionRect *topRect = [ASTextSelectionRect new]; - topRect.isVertical = isVertical; - CGFloat topOffset = [self offsetForTextPosition:range.start.offset lineIndex:startLineIndex]; - CTRunRef topRun = [self _runForLine:startLine position:range.start]; - if (topRun && (CTRunGetStatus(topRun) & kCTRunStatusRightToLeft)) { - if (isVertical) { - topRect.rect = CGRectMake(startLine.left, _container.path ? startLine.top : _container.insets.top, startLine.width, topOffset - startLine.top); - } else { - topRect.rect = CGRectMake(_container.path ? startLine.left : _container.insets.left, startLine.top, topOffset - startLine.left, startLine.height); - } - topRect.writingDirection = UITextWritingDirectionRightToLeft; - } else { - if (isVertical) { - topRect.rect = CGRectMake(startLine.left, topOffset, startLine.width, (_container.path ? startLine.bottom : _container.size.height - _container.insets.bottom) - topOffset); - } else { - topRect.rect = CGRectMake(topOffset, startLine.top, (_container.path ? startLine.right : _container.size.width - _container.insets.right) - topOffset, startLine.height); - } - } - [rects addObject:topRect]; - - // end line select rect - ASTextSelectionRect *bottomRect = [ASTextSelectionRect new]; - bottomRect.isVertical = isVertical; - CGFloat bottomOffset = [self offsetForTextPosition:range.end.offset lineIndex:endLineIndex]; - CTRunRef bottomRun = [self _runForLine:endLine position:range.end]; - if (bottomRun && (CTRunGetStatus(bottomRun) & kCTRunStatusRightToLeft)) { - if (isVertical) { - bottomRect.rect = CGRectMake(endLine.left, bottomOffset, endLine.width, (_container.path ? endLine.bottom : _container.size.height - _container.insets.bottom) - bottomOffset); - } else { - bottomRect.rect = CGRectMake(bottomOffset, endLine.top, (_container.path ? endLine.right : _container.size.width - _container.insets.right) - bottomOffset, endLine.height); - } - bottomRect.writingDirection = UITextWritingDirectionRightToLeft; - } else { - if (isVertical) { - CGFloat top = _container.path ? endLine.top : _container.insets.top; - bottomRect.rect = CGRectMake(endLine.left, top, endLine.width, bottomOffset - top); - } else { - CGFloat left = _container.path ? endLine.left : _container.insets.left; - bottomRect.rect = CGRectMake(left, endLine.top, bottomOffset - left, endLine.height); - } - } - [rects addObject:bottomRect]; - - if (endLineIndex - startLineIndex >= 2) { - CGRect r = CGRectZero; - BOOL startLineDetected = NO; - for (NSUInteger l = startLineIndex + 1; l < endLineIndex; l++) { - ASTextLine *line = _lines[l]; - if (line.row == startLine.row || line.row == endLine.row) continue; - if (!startLineDetected) { - r = line.bounds; - startLineDetected = YES; - } else { - r = CGRectUnion(r, line.bounds); - } - } - if (startLineDetected) { - if (isVertical) { - if (!_container.path) { - r.origin.y = _container.insets.top; - r.size.height = _container.size.height - _container.insets.bottom - _container.insets.top; - } - r.size.width = CGRectGetMinX(topRect.rect) - CGRectGetMaxX(bottomRect.rect); - r.origin.x = CGRectGetMaxX(bottomRect.rect); - } else { - if (!_container.path) { - r.origin.x = _container.insets.left; - r.size.width = _container.size.width - _container.insets.right - _container.insets.left; - } - r.origin.y = CGRectGetMaxY(topRect.rect); - r.size.height = bottomRect.rect.origin.y - r.origin.y; - } - - ASTextSelectionRect *rect = [ASTextSelectionRect new]; - rect.rect = r; - rect.isVertical = isVertical; - [rects addObject:rect]; - } - } else { - if (isVertical) { - CGRect r0 = bottomRect.rect; - CGRect r1 = topRect.rect; - CGFloat mid = (CGRectGetMaxX(r0) + CGRectGetMinX(r1)) * 0.5; - r0.size.width = mid - r0.origin.x; - CGFloat r1ofs = r1.origin.x - mid; - r1.origin.x -= r1ofs; - r1.size.width += r1ofs; - topRect.rect = r1; - bottomRect.rect = r0; - } else { - CGRect r0 = topRect.rect; - CGRect r1 = bottomRect.rect; - CGFloat mid = (CGRectGetMaxY(r0) + CGRectGetMinY(r1)) * 0.5; - r0.size.height = mid - r0.origin.y; - CGFloat r1ofs = r1.origin.y - mid; - r1.origin.y -= r1ofs; - r1.size.height += r1ofs; - topRect.rect = r0; - bottomRect.rect = r1; - } - } - } - return rects; -} - -- (NSArray *)selectionRectsWithoutStartAndEndForRange:(ASTextRange *)range { - NSMutableArray *rects = [self selectionRectsForRange:range].mutableCopy; - for (NSInteger i = 0, max = rects.count; i < max; i++) { - ASTextSelectionRect *rect = rects[i]; - if (rect.containsStart || rect.containsEnd) { - [rects removeObjectAtIndex:i]; - i--; - max--; - } - } - return rects; -} - -- (NSArray *)selectionRectsWithOnlyStartAndEndForRange:(ASTextRange *)range { - NSMutableArray *rects = [self selectionRectsForRange:range].mutableCopy; - for (NSInteger i = 0, max = rects.count; i < max; i++) { - ASTextSelectionRect *rect = rects[i]; - if (!rect.containsStart && !rect.containsEnd) { - [rects removeObjectAtIndex:i]; - i--; - max--; - } - } - return rects; -} - - -#pragma mark - Draw - - -typedef NS_OPTIONS(NSUInteger, ASTextDecorationType) { - ASTextDecorationTypeUnderline = 1 << 0, - ASTextDecorationTypeStrikethrough = 1 << 1, -}; - -typedef NS_OPTIONS(NSUInteger, ASTextBorderType) { - ASTextBorderTypeBackgound = 1 << 0, - ASTextBorderTypeNormal = 1 << 1, -}; - -static CGRect ASTextMergeRectInSameLine(CGRect rect1, CGRect rect2, BOOL isVertical) { - if (isVertical) { - CGFloat top = MIN(rect1.origin.y, rect2.origin.y); - CGFloat bottom = MAX(rect1.origin.y + rect1.size.height, rect2.origin.y + rect2.size.height); - CGFloat width = MAX(rect1.size.width, rect2.size.width); - return CGRectMake(rect1.origin.x, top, width, bottom - top); - } else { - CGFloat left = MIN(rect1.origin.x, rect2.origin.x); - CGFloat right = MAX(rect1.origin.x + rect1.size.width, rect2.origin.x + rect2.size.width); - CGFloat height = MAX(rect1.size.height, rect2.size.height); - return CGRectMake(left, rect1.origin.y, right - left, height); - } -} - -static void ASTextGetRunsMaxMetric(CFArrayRef runs, CGFloat *xHeight, CGFloat *underlinePosition, CGFloat *lineThickness) { - CGFloat maxXHeight = 0; - CGFloat maxUnderlinePos = 0; - CGFloat maxLineThickness = 0; - for (NSUInteger i = 0, max = CFArrayGetCount(runs); i < max; i++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, i); - CFDictionaryRef attrs = CTRunGetAttributes(run); - if (attrs) { - CTFontRef font = (CTFontRef)CFDictionaryGetValue(attrs, kCTFontAttributeName); - if (font) { - CGFloat xHeight = CTFontGetXHeight(font); - if (xHeight > maxXHeight) maxXHeight = xHeight; - CGFloat underlinePos = CTFontGetUnderlinePosition(font); - if (underlinePos < maxUnderlinePos) maxUnderlinePos = underlinePos; - CGFloat lineThickness = CTFontGetUnderlineThickness(font); - if (lineThickness > maxLineThickness) maxLineThickness = lineThickness; - } - } - } - if (xHeight) *xHeight = maxXHeight; - if (underlinePosition) *underlinePosition = maxUnderlinePos; - if (lineThickness) *lineThickness = maxLineThickness; -} - -static void ASTextDrawRun(ASTextLine *line, CTRunRef run, CGContextRef context, CGSize size, BOOL isVertical, NSArray *runRanges, CGFloat verticalOffset) { - CGAffineTransform runTextMatrix = CTRunGetTextMatrix(run); - BOOL runTextMatrixIsID = CGAffineTransformIsIdentity(runTextMatrix); - - CFDictionaryRef runAttrs = CTRunGetAttributes(run); - NSValue *glyphTransformValue = (NSValue *)CFDictionaryGetValue(runAttrs, (__bridge const void *)(ASTextGlyphTransformAttributeName)); - if (!isVertical && !glyphTransformValue) { // draw run - if (!runTextMatrixIsID) { - CGContextSaveGState(context); - CGAffineTransform trans = CGContextGetTextMatrix(context); - CGContextSetTextMatrix(context, CGAffineTransformConcat(trans, runTextMatrix)); - } - CTRunDraw(run, context, CFRangeMake(0, 0)); - if (!runTextMatrixIsID) { - CGContextRestoreGState(context); - } - } else { // draw glyph - CTFontRef runFont = (CTFontRef)CFDictionaryGetValue(runAttrs, kCTFontAttributeName); - if (!runFont) return; - NSUInteger glyphCount = CTRunGetGlyphCount(run); - if (glyphCount <= 0) return; - - CGGlyph glyphs[glyphCount]; - CGPoint glyphPositions[glyphCount]; - CTRunGetGlyphs(run, CFRangeMake(0, 0), glyphs); - CTRunGetPositions(run, CFRangeMake(0, 0), glyphPositions); - - CGColorRef fillColor = (CGColorRef)CFDictionaryGetValue(runAttrs, kCTForegroundColorAttributeName); - fillColor = ASTextGetCGColor(fillColor); - NSNumber *strokeWidth = (NSNumber *)CFDictionaryGetValue(runAttrs, kCTStrokeWidthAttributeName); - - CGContextSaveGState(context); { - CGContextSetFillColorWithColor(context, fillColor); - if (!strokeWidth || strokeWidth.floatValue == 0) { - CGContextSetTextDrawingMode(context, kCGTextFill); - } else { - CGColorRef strokeColor = (CGColorRef)CFDictionaryGetValue(runAttrs, kCTStrokeColorAttributeName); - if (!strokeColor) strokeColor = fillColor; - CGContextSetStrokeColorWithColor(context, strokeColor); - CGContextSetLineWidth(context, CTFontGetSize(runFont) * fabs(strokeWidth.floatValue * 0.01)); - if (strokeWidth.floatValue > 0) { - CGContextSetTextDrawingMode(context, kCGTextStroke); - } else { - CGContextSetTextDrawingMode(context, kCGTextFillStroke); - } - } - - if (isVertical) { - CFIndex runStrIdx[glyphCount + 1]; - CTRunGetStringIndices(run, CFRangeMake(0, 0), runStrIdx); - CFRange runStrRange = CTRunGetStringRange(run); - runStrIdx[glyphCount] = runStrRange.location + runStrRange.length; - CGSize glyphAdvances[glyphCount]; - CTRunGetAdvances(run, CFRangeMake(0, 0), glyphAdvances); - CGFloat ascent = CTFontGetAscent(runFont); - CGFloat descent = CTFontGetDescent(runFont); - CGAffineTransform glyphTransform = glyphTransformValue.CGAffineTransformValue; - CGPoint zeroPoint = CGPointZero; - - for (ASTextRunGlyphRange *oneRange in runRanges) { - NSRange range = oneRange.glyphRangeInRun; - NSUInteger rangeMax = range.location + range.length; - ASTextRunGlyphDrawMode mode = oneRange.drawMode; - - for (NSUInteger g = range.location; g < rangeMax; g++) { - CGContextSaveGState(context); { - CGContextSetTextMatrix(context, CGAffineTransformIdentity); - if (glyphTransformValue) { - CGContextSetTextMatrix(context, glyphTransform); - } - if (mode) { // CJK glyph, need rotated - CGFloat ofs = (ascent - descent) * 0.5; - CGFloat w = glyphAdvances[g].width * 0.5; - CGFloat x = x = line.position.x + verticalOffset + glyphPositions[g].y + (ofs - w); - CGFloat y = -line.position.y + size.height - glyphPositions[g].x - (ofs + w); - if (mode == ASTextRunGlyphDrawModeVerticalRotateMove) { - x += w; - y += w; - } - CGContextSetTextPosition(context, x, y); - } else { - CGContextRotateCTM(context, -M_PI_2); - CGContextSetTextPosition(context, - line.position.y - size.height + glyphPositions[g].x, - line.position.x + verticalOffset + glyphPositions[g].y); - } - - if (ASTextCTFontContainsColorBitmapGlyphs(runFont)) { - CTFontDrawGlyphs(runFont, glyphs + g, &zeroPoint, 1, context); - } else { - CGFontRef cgFont = CTFontCopyGraphicsFont(runFont, NULL); - CGContextSetFont(context, cgFont); - CGContextSetFontSize(context, CTFontGetSize(runFont)); - CGContextShowGlyphsAtPositions(context, glyphs + g, &zeroPoint, 1); - CGFontRelease(cgFont); - } - } CGContextRestoreGState(context); - } - } - } else { // not vertical - if (glyphTransformValue) { - CFIndex runStrIdx[glyphCount + 1]; - CTRunGetStringIndices(run, CFRangeMake(0, 0), runStrIdx); - CFRange runStrRange = CTRunGetStringRange(run); - runStrIdx[glyphCount] = runStrRange.location + runStrRange.length; - CGSize glyphAdvances[glyphCount]; - CTRunGetAdvances(run, CFRangeMake(0, 0), glyphAdvances); - CGAffineTransform glyphTransform = glyphTransformValue.CGAffineTransformValue; - CGPoint zeroPoint = CGPointZero; - - for (NSUInteger g = 0; g < glyphCount; g++) { - CGContextSaveGState(context); { - CGContextSetTextMatrix(context, CGAffineTransformIdentity); - CGContextSetTextMatrix(context, glyphTransform); - CGContextSetTextPosition(context, - line.position.x + glyphPositions[g].x, - size.height - (line.position.y + glyphPositions[g].y)); - - if (ASTextCTFontContainsColorBitmapGlyphs(runFont)) { - CTFontDrawGlyphs(runFont, glyphs + g, &zeroPoint, 1, context); - } else { - CGFontRef cgFont = CTFontCopyGraphicsFont(runFont, NULL); - CGContextSetFont(context, cgFont); - CGContextSetFontSize(context, CTFontGetSize(runFont)); - CGContextShowGlyphsAtPositions(context, glyphs + g, &zeroPoint, 1); - CGFontRelease(cgFont); - } - } CGContextRestoreGState(context); - } - } else { - if (ASTextCTFontContainsColorBitmapGlyphs(runFont)) { - CTFontDrawGlyphs(runFont, glyphs, glyphPositions, glyphCount, context); - } else { - CGFontRef cgFont = CTFontCopyGraphicsFont(runFont, NULL); - CGContextSetFont(context, cgFont); - CGContextSetFontSize(context, CTFontGetSize(runFont)); - CGContextShowGlyphsAtPositions(context, glyphs, glyphPositions, glyphCount); - CGFontRelease(cgFont); - } - } - } - - } CGContextRestoreGState(context); - } -} - -static void ASTextSetLinePatternInContext(ASTextLineStyle style, CGFloat width, CGFloat phase, CGContextRef context){ - CGContextSetLineWidth(context, width); - CGContextSetLineCap(context, kCGLineCapButt); - CGContextSetLineJoin(context, kCGLineJoinMiter); - - CGFloat dash = 12, dot = 5, space = 3; - NSUInteger pattern = style & 0xF00; - if (pattern == ASTextLineStylePatternSolid) { - CGContextSetLineDash(context, phase, NULL, 0); - } else if (pattern == ASTextLineStylePatternDot) { - CGFloat lengths[2] = {width * dot, width * space}; - CGContextSetLineDash(context, phase, lengths, 2); - } else if (pattern == ASTextLineStylePatternDash) { - CGFloat lengths[2] = {width * dash, width * space}; - CGContextSetLineDash(context, phase, lengths, 2); - } else if (pattern == ASTextLineStylePatternDashDot) { - CGFloat lengths[4] = {width * dash, width * space, width * dot, width * space}; - CGContextSetLineDash(context, phase, lengths, 4); - } else if (pattern == ASTextLineStylePatternDashDotDot) { - CGFloat lengths[6] = {width * dash, width * space,width * dot, width * space, width * dot, width * space}; - CGContextSetLineDash(context, phase, lengths, 6); - } else if (pattern == ASTextLineStylePatternCircleDot) { - CGFloat lengths[2] = {width * 0, width * 3}; - CGContextSetLineDash(context, phase, lengths, 2); - CGContextSetLineCap(context, kCGLineCapRound); - CGContextSetLineJoin(context, kCGLineJoinRound); - } -} - - -static void ASTextDrawBorderRects(CGContextRef context, CGSize size, ASTextBorder *border, NSArray *rects, BOOL isVertical) { - if (rects.count == 0) return; - - ASTextShadow *shadow = border.shadow; - if (shadow.color) { - CGContextSaveGState(context); - CGContextSetShadowWithColor(context, shadow.offset, shadow.radius, shadow.color.CGColor); - CGContextBeginTransparencyLayer(context, NULL); - } - - NSMutableArray *paths = [NSMutableArray new]; - for (NSValue *value in rects) { - CGRect rect = value.CGRectValue; - if (isVertical) { - rect = UIEdgeInsetsInsetRect(rect, UIEdgeInsetRotateVertical(border.insets)); - } else { - rect = UIEdgeInsetsInsetRect(rect, border.insets); - } - rect = ASTextCGRectPixelRound(rect); - UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:border.cornerRadius]; - [path closePath]; - [paths addObject:path]; - } - - if (border.fillColor) { - CGContextSaveGState(context); - CGContextSetFillColorWithColor(context, border.fillColor.CGColor); - for (UIBezierPath *path in paths) { - CGContextAddPath(context, path.CGPath); - } - CGContextFillPath(context); - CGContextRestoreGState(context); - } - - if (border.strokeColor && border.lineStyle > 0 && border.strokeWidth > 0) { - - //-------------------------- single line ------------------------------// - CGContextSaveGState(context); - for (UIBezierPath *path in paths) { - CGRect bounds = CGRectUnion(path.bounds, (CGRect){CGPointZero, size}); - bounds = CGRectInset(bounds, -2 * border.strokeWidth, -2 * border.strokeWidth); - CGContextAddRect(context, bounds); - CGContextAddPath(context, path.CGPath); - CGContextEOClip(context); - } - [border.strokeColor setStroke]; - ASTextSetLinePatternInContext(border.lineStyle, border.strokeWidth, 0, context); - CGFloat inset = -border.strokeWidth * 0.5; - if ((border.lineStyle & 0xFF) == ASTextLineStyleThick) { - inset *= 2; - CGContextSetLineWidth(context, border.strokeWidth * 2); - } - CGFloat radiusDelta = -inset; - if (border.cornerRadius <= 0) { - radiusDelta = 0; - } - CGContextSetLineJoin(context, border.lineJoin); - for (NSValue *value in rects) { - CGRect rect = value.CGRectValue; - if (isVertical) { - rect = UIEdgeInsetsInsetRect(rect, UIEdgeInsetRotateVertical(border.insets)); - } else { - rect = UIEdgeInsetsInsetRect(rect, border.insets); - } - rect = CGRectInset(rect, inset, inset); - UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:border.cornerRadius + radiusDelta]; - [path closePath]; - CGContextAddPath(context, path.CGPath); - } - CGContextStrokePath(context); - CGContextRestoreGState(context); - - //------------------------- second line ------------------------------// - if ((border.lineStyle & 0xFF) == ASTextLineStyleDouble) { - CGContextSaveGState(context); - CGFloat inset = -border.strokeWidth * 2; - for (NSValue *value in rects) { - CGRect rect = value.CGRectValue; - rect = UIEdgeInsetsInsetRect(rect, border.insets); - rect = CGRectInset(rect, inset, inset); - UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:border.cornerRadius + 2 * border.strokeWidth]; - [path closePath]; - - CGRect bounds = CGRectUnion(path.bounds, (CGRect){CGPointZero, size}); - bounds = CGRectInset(bounds, -2 * border.strokeWidth, -2 * border.strokeWidth); - CGContextAddRect(context, bounds); - CGContextAddPath(context, path.CGPath); - CGContextEOClip(context); - } - CGContextSetStrokeColorWithColor(context, border.strokeColor.CGColor); - ASTextSetLinePatternInContext(border.lineStyle, border.strokeWidth, 0, context); - CGContextSetLineJoin(context, border.lineJoin); - inset = -border.strokeWidth * 2.5; - radiusDelta = border.strokeWidth * 2; - if (border.cornerRadius <= 0) { - radiusDelta = 0; - } - for (NSValue *value in rects) { - CGRect rect = value.CGRectValue; - rect = UIEdgeInsetsInsetRect(rect, border.insets); - rect = CGRectInset(rect, inset, inset); - UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:border.cornerRadius + radiusDelta]; - [path closePath]; - CGContextAddPath(context, path.CGPath); - } - CGContextStrokePath(context); - CGContextRestoreGState(context); - } - } - - if (shadow.color) { - CGContextEndTransparencyLayer(context); - CGContextRestoreGState(context); - } -} - -static void ASTextDrawLineStyle(CGContextRef context, CGFloat length, CGFloat lineWidth, ASTextLineStyle style, CGPoint position, CGColorRef color, BOOL isVertical) { - NSUInteger styleBase = style & 0xFF; - if (styleBase == 0) return; - - CGContextSaveGState(context); { - if (isVertical) { - CGFloat x, y1, y2, w; - y1 = ASRoundPixelValue(position.y); - y2 = ASRoundPixelValue(position.y + length); - w = (styleBase == ASTextLineStyleThick ? lineWidth * 2 : lineWidth); - - CGFloat linePixel = ASTextCGFloatToPixel(w); - if (fabs(linePixel - floor(linePixel)) < 0.1) { - int iPixel = linePixel; - if (iPixel == 0 || (iPixel % 2)) { // odd line pixel - x = ASTextCGFloatPixelHalf(position.x); - } else { - x = ASFloorPixelValue(position.x); - } - } else { - x = position.x; - } - - CGContextSetStrokeColorWithColor(context, color); - ASTextSetLinePatternInContext(style, lineWidth, position.y, context); - CGContextSetLineWidth(context, w); - if (styleBase == ASTextLineStyleSingle) { - CGContextMoveToPoint(context, x, y1); - CGContextAddLineToPoint(context, x, y2); - CGContextStrokePath(context); - } else if (styleBase == ASTextLineStyleThick) { - CGContextMoveToPoint(context, x, y1); - CGContextAddLineToPoint(context, x, y2); - CGContextStrokePath(context); - } else if (styleBase == ASTextLineStyleDouble) { - CGContextMoveToPoint(context, x - w, y1); - CGContextAddLineToPoint(context, x - w, y2); - CGContextStrokePath(context); - CGContextMoveToPoint(context, x + w, y1); - CGContextAddLineToPoint(context, x + w, y2); - CGContextStrokePath(context); - } - } else { - CGFloat x1, x2, y, w; - x1 = ASRoundPixelValue(position.x); - x2 = ASRoundPixelValue(position.x + length); - w = (styleBase == ASTextLineStyleThick ? lineWidth * 2 : lineWidth); - - CGFloat linePixel = ASTextCGFloatToPixel(w); - if (fabs(linePixel - floor(linePixel)) < 0.1) { - int iPixel = linePixel; - if (iPixel == 0 || (iPixel % 2)) { // odd line pixel - y = ASTextCGFloatPixelHalf(position.y); - } else { - y = ASFloorPixelValue(position.y); - } - } else { - y = position.y; - } - - CGContextSetStrokeColorWithColor(context, color); - ASTextSetLinePatternInContext(style, lineWidth, position.x, context); - CGContextSetLineWidth(context, w); - if (styleBase == ASTextLineStyleSingle) { - CGContextMoveToPoint(context, x1, y); - CGContextAddLineToPoint(context, x2, y); - CGContextStrokePath(context); - } else if (styleBase == ASTextLineStyleThick) { - CGContextMoveToPoint(context, x1, y); - CGContextAddLineToPoint(context, x2, y); - CGContextStrokePath(context); - } else if (styleBase == ASTextLineStyleDouble) { - CGContextMoveToPoint(context, x1, y - w); - CGContextAddLineToPoint(context, x2, y - w); - CGContextStrokePath(context); - CGContextMoveToPoint(context, x1, y + w); - CGContextAddLineToPoint(context, x2, y + w); - CGContextStrokePath(context); - } - } - } CGContextRestoreGState(context); -} - -static void ASTextDrawText(ASTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, BOOL (^cancel)(void)) { - CGContextSaveGState(context); { - - CGContextTranslateCTM(context, point.x, point.y); - CGContextTranslateCTM(context, 0, size.height); - CGContextScaleCTM(context, 1, -1); - - BOOL isVertical = layout.container.verticalForm; - CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0; - - NSArray *lines = layout.lines; - for (NSUInteger l = 0, lMax = lines.count; l < lMax; l++) { - ASTextLine *line = lines[l]; - if (layout.truncatedLine && layout.truncatedLine.index == line.index) line = layout.truncatedLine; - NSArray *lineRunRanges = line.verticalRotateRange; - CGFloat posX = line.position.x + verticalOffset; - CGFloat posY = size.height - line.position.y; - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSUInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - CGContextSetTextMatrix(context, CGAffineTransformIdentity); - CGContextSetTextPosition(context, posX, posY); - ASTextDrawRun(line, run, context, size, isVertical, lineRunRanges[r], verticalOffset); - } - if (cancel && cancel()) break; - } - - // Use this to draw frame for test/debug. - // CGContextTranslateCTM(context, verticalOffset, size.height); - // CTFrameDraw(layout.frame, context); - - } CGContextRestoreGState(context); -} - -static void ASTextDrawBlockBorder(ASTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, BOOL (^cancel)(void)) { - CGContextSaveGState(context); - CGContextTranslateCTM(context, point.x, point.y); - - BOOL isVertical = layout.container.verticalForm; - CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0; - - NSArray *lines = layout.lines; - for (NSInteger l = 0, lMax = lines.count; l < lMax; l++) { - if (cancel && cancel()) break; - - ASTextLine *line = lines[l]; - if (layout.truncatedLine && layout.truncatedLine.index == line.index) line = layout.truncatedLine; - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - CFIndex glyphCount = CTRunGetGlyphCount(run); - if (glyphCount == 0) continue; - NSDictionary *attrs = (id)CTRunGetAttributes(run); - ASTextBorder *border = attrs[ASTextBlockBorderAttributeName]; - if (!border) continue; - - NSUInteger lineStartIndex = line.index; - while (lineStartIndex > 0) { - if (((ASTextLine *)lines[lineStartIndex - 1]).row == line.row) lineStartIndex--; - else break; - } - - CGRect unionRect = CGRectZero; - NSUInteger lineStartRow = ((ASTextLine *)lines[lineStartIndex]).row; - NSUInteger lineContinueIndex = lineStartIndex; - NSUInteger lineContinueRow = lineStartRow; - do { - ASTextLine *one = lines[lineContinueIndex]; - if (lineContinueIndex == lineStartIndex) { - unionRect = one.bounds; - } else { - unionRect = CGRectUnion(unionRect, one.bounds); - } - if (lineContinueIndex + 1 == lMax) break; - ASTextLine *next = lines[lineContinueIndex + 1]; - if (next.row != lineContinueRow) { - ASTextBorder *nextBorder = [layout.text as_attribute:ASTextBlockBorderAttributeName atIndex:next.range.location]; - if ([nextBorder isEqual:border]) { - lineContinueRow++; - } else { - break; - } - } - lineContinueIndex++; - } while (true); - - if (isVertical) { - UIEdgeInsets insets = layout.container.insets; - unionRect.origin.y = insets.top; - unionRect.size.height = layout.container.size.height -insets.top - insets.bottom; - } else { - UIEdgeInsets insets = layout.container.insets; - unionRect.origin.x = insets.left; - unionRect.size.width = layout.container.size.width -insets.left - insets.right; - } - unionRect.origin.x += verticalOffset; - ASTextDrawBorderRects(context, size, border, @[[NSValue valueWithCGRect:unionRect]], isVertical); - - l = lineContinueIndex; - break; - } - } - - - CGContextRestoreGState(context); -} - -static void ASTextDrawBorder(ASTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, ASTextBorderType type, BOOL (^cancel)(void)) { - CGContextSaveGState(context); - CGContextTranslateCTM(context, point.x, point.y); - - BOOL isVertical = layout.container.verticalForm; - CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0; - - NSArray *lines = layout.lines; - NSString *borderKey = (type == ASTextBorderTypeNormal ? ASTextBorderAttributeName : ASTextBackgroundBorderAttributeName); - - BOOL needJumpRun = NO; - NSUInteger jumpRunIndex = 0; - - for (NSInteger l = 0, lMax = lines.count; l < lMax; l++) { - if (cancel && cancel()) break; - - ASTextLine *line = lines[l]; - if (layout.truncatedLine && layout.truncatedLine.index == line.index) line = layout.truncatedLine; - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) { - if (needJumpRun) { - needJumpRun = NO; - r = jumpRunIndex + 1; - if (r >= rMax) break; - } - - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - CFIndex glyphCount = CTRunGetGlyphCount(run); - if (glyphCount == 0) continue; - - NSDictionary *attrs = (id)CTRunGetAttributes(run); - ASTextBorder *border = attrs[borderKey]; - if (!border) continue; - - CFRange runRange = CTRunGetStringRange(run); - if (runRange.location == kCFNotFound || runRange.length == 0) continue; - if (runRange.location + runRange.length > layout.text.length) continue; - - NSMutableArray *runRects = [NSMutableArray new]; - NSInteger endLineIndex = l; - NSInteger endRunIndex = r; - BOOL endFound = NO; - for (NSInteger ll = l; ll < lMax; ll++) { - if (endFound) break; - ASTextLine *iLine = lines[ll]; - CFArrayRef iRuns = CTLineGetGlyphRuns(iLine.CTLine); - - CGRect extLineRect = CGRectNull; - for (NSInteger rr = (ll == l) ? r : 0, rrMax = CFArrayGetCount(iRuns); rr < rrMax; rr++) { - CTRunRef iRun = (CTRunRef)CFArrayGetValueAtIndex(iRuns, rr); - NSDictionary *iAttrs = (id)CTRunGetAttributes(iRun); - ASTextBorder *iBorder = iAttrs[borderKey]; - if (![border isEqual:iBorder]) { - endFound = YES; - break; - } - endLineIndex = ll; - endRunIndex = rr; - - CGPoint iRunPosition = CGPointZero; - CTRunGetPositions(iRun, CFRangeMake(0, 1), &iRunPosition); - CGFloat ascent, descent; - CGFloat iRunWidth = CTRunGetTypographicBounds(iRun, CFRangeMake(0, 0), &ascent, &descent, NULL); - - if (isVertical) { - ASTEXT_SWAP(iRunPosition.x, iRunPosition.y); - iRunPosition.y += iLine.position.y; - CGRect iRect = CGRectMake(verticalOffset + line.position.x - descent, iRunPosition.y, ascent + descent, iRunWidth); - if (CGRectIsNull(extLineRect)) { - extLineRect = iRect; - } else { - extLineRect = CGRectUnion(extLineRect, iRect); - } - } else { - iRunPosition.x += iLine.position.x; - CGRect iRect = CGRectMake(iRunPosition.x, iLine.position.y - ascent, iRunWidth, ascent + descent); - if (CGRectIsNull(extLineRect)) { - extLineRect = iRect; - } else { - extLineRect = CGRectUnion(extLineRect, iRect); - } - } - } - - if (!CGRectIsNull(extLineRect)) { - [runRects addObject:[NSValue valueWithCGRect:extLineRect]]; - } - } - - NSMutableArray *drawRects = [NSMutableArray new]; - CGRect curRect= ((NSValue *)[runRects firstObject]).CGRectValue; - for (NSInteger re = 0, reMax = runRects.count; re < reMax; re++) { - CGRect rect = ((NSValue *)runRects[re]).CGRectValue; - if (isVertical) { - if (fabs(rect.origin.x - curRect.origin.x) < 1) { - curRect = ASTextMergeRectInSameLine(rect, curRect, isVertical); - } else { - [drawRects addObject:[NSValue valueWithCGRect:curRect]]; - curRect = rect; - } - } else { - if (fabs(rect.origin.y - curRect.origin.y) < 1) { - curRect = ASTextMergeRectInSameLine(rect, curRect, isVertical); - } else { - [drawRects addObject:[NSValue valueWithCGRect:curRect]]; - curRect = rect; - } - } - } - if (!CGRectEqualToRect(curRect, CGRectZero)) { - [drawRects addObject:[NSValue valueWithCGRect:curRect]]; - } - - ASTextDrawBorderRects(context, size, border, drawRects, isVertical); - - if (l == endLineIndex) { - r = endRunIndex; - } else { - l = endLineIndex - 1; - needJumpRun = YES; - jumpRunIndex = endRunIndex; - break; - } - - } - } - - CGContextRestoreGState(context); -} - -static void ASTextDrawDecoration(ASTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, ASTextDecorationType type, BOOL (^cancel)(void)) { - NSArray *lines = layout.lines; - - CGContextSaveGState(context); - CGContextTranslateCTM(context, point.x, point.y); - - BOOL isVertical = layout.container.verticalForm; - CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0; - CGContextTranslateCTM(context, verticalOffset, 0); - - for (NSUInteger l = 0, lMax = layout.lines.count; l < lMax; l++) { - if (cancel && cancel()) break; - - ASTextLine *line = lines[l]; - if (layout.truncatedLine && layout.truncatedLine.index == line.index) line = layout.truncatedLine; - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSUInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - CFIndex glyphCount = CTRunGetGlyphCount(run); - if (glyphCount == 0) continue; - - NSDictionary *attrs = (id)CTRunGetAttributes(run); - ASTextDecoration *underline = attrs[ASTextUnderlineAttributeName]; - ASTextDecoration *strikethrough = attrs[ASTextStrikethroughAttributeName]; - - BOOL needDrawUnderline = NO, needDrawStrikethrough = NO; - if ((type & ASTextDecorationTypeUnderline) && underline.style > 0) { - needDrawUnderline = YES; - } - if ((type & ASTextDecorationTypeStrikethrough) && strikethrough.style > 0) { - needDrawStrikethrough = YES; - } - if (!needDrawUnderline && !needDrawStrikethrough) continue; - - CFRange runRange = CTRunGetStringRange(run); - if (runRange.location == kCFNotFound || runRange.length == 0) continue; - if (runRange.location + runRange.length > layout.text.length) continue; - NSString *runStr = [layout.text attributedSubstringFromRange:NSMakeRange(runRange.location, runRange.length)].string; - if (ASTextIsLinebreakString(runStr)) continue; // may need more checks... - - CGFloat xHeight, underlinePosition, lineThickness; - ASTextGetRunsMaxMetric(runs, &xHeight, &underlinePosition, &lineThickness); - - CGPoint underlineStart, strikethroughStart; - CGFloat length; - - if (isVertical) { - underlineStart.x = line.position.x + underlinePosition; - strikethroughStart.x = line.position.x + xHeight / 2; - - CGPoint runPosition = CGPointZero; - CTRunGetPositions(run, CFRangeMake(0, 1), &runPosition); - underlineStart.y = strikethroughStart.y = runPosition.x + line.position.y; - length = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), NULL, NULL, NULL); - - } else { - underlineStart.y = line.position.y - underlinePosition; - strikethroughStart.y = line.position.y - xHeight / 2; - - CGPoint runPosition = CGPointZero; - CTRunGetPositions(run, CFRangeMake(0, 1), &runPosition); - underlineStart.x = strikethroughStart.x = runPosition.x + line.position.x; - length = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), NULL, NULL, NULL); - } - - if (needDrawUnderline) { - CGColorRef color = underline.color.CGColor; - if (!color) { - color = (__bridge CGColorRef)(attrs[(id)kCTForegroundColorAttributeName]); - color = ASTextGetCGColor(color); - } - CGFloat thickness = underline.width ? underline.width.floatValue : lineThickness; - ASTextShadow *shadow = underline.shadow; - while (shadow) { - if (!shadow.color) { - shadow = shadow.subShadow; - continue; - } - CGFloat offsetAlterX = size.width + 0xFFFF; - CGContextSaveGState(context); { - CGSize offset = shadow.offset; - offset.width -= offsetAlterX; - CGContextSaveGState(context); { - CGContextSetShadowWithColor(context, offset, shadow.radius, shadow.color.CGColor); - CGContextSetBlendMode(context, shadow.blendMode); - CGContextTranslateCTM(context, offsetAlterX, 0); - ASTextDrawLineStyle(context, length, thickness, underline.style, underlineStart, color, isVertical); - } CGContextRestoreGState(context); - } CGContextRestoreGState(context); - shadow = shadow.subShadow; - } - ASTextDrawLineStyle(context, length, thickness, underline.style, underlineStart, color, isVertical); - } - - if (needDrawStrikethrough) { - CGColorRef color = strikethrough.color.CGColor; - if (!color) { - color = (__bridge CGColorRef)(attrs[(id)kCTForegroundColorAttributeName]); - color = ASTextGetCGColor(color); - } - CGFloat thickness = strikethrough.width ? strikethrough.width.floatValue : lineThickness; - ASTextShadow *shadow = underline.shadow; - while (shadow) { - if (!shadow.color) { - shadow = shadow.subShadow; - continue; - } - CGFloat offsetAlterX = size.width + 0xFFFF; - CGContextSaveGState(context); { - CGSize offset = shadow.offset; - offset.width -= offsetAlterX; - CGContextSaveGState(context); { - CGContextSetShadowWithColor(context, offset, shadow.radius, shadow.color.CGColor); - CGContextSetBlendMode(context, shadow.blendMode); - CGContextTranslateCTM(context, offsetAlterX, 0); - ASTextDrawLineStyle(context, length, thickness, underline.style, underlineStart, color, isVertical); - } CGContextRestoreGState(context); - } CGContextRestoreGState(context); - shadow = shadow.subShadow; - } - ASTextDrawLineStyle(context, length, thickness, strikethrough.style, strikethroughStart, color, isVertical); - } - } - } - CGContextRestoreGState(context); -} - -static void ASTextDrawAttachment(ASTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, UIView *targetView, CALayer *targetLayer, BOOL (^cancel)(void)) { - - BOOL isVertical = layout.container.verticalForm; - CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0; - - for (NSUInteger i = 0, max = layout.attachments.count; i < max; i++) { - ASTextAttachment *a = layout.attachments[i]; - if (!a.content) continue; - - UIImage *image = nil; - UIView *view = nil; - CALayer *layer = nil; - if ([a.content isKindOfClass:[UIImage class]]) { - image = a.content; - } else if ([a.content isKindOfClass:[UIView class]]) { - view = a.content; - } else if ([a.content isKindOfClass:[CALayer class]]) { - layer = a.content; - } - if (!image && !view && !layer) continue; - if (image && !context) continue; - if (view && !targetView) continue; - if (layer && !targetLayer) continue; - if (cancel && cancel()) break; - - CGSize asize = image ? image.size : view ? view.frame.size : layer.frame.size; - CGRect rect = ((NSValue *)layout.attachmentRects[i]).CGRectValue; - if (isVertical) { - rect = UIEdgeInsetsInsetRect(rect, UIEdgeInsetRotateVertical(a.contentInsets)); - } else { - rect = UIEdgeInsetsInsetRect(rect, a.contentInsets); - } - rect = ASTextCGRectFitWithContentMode(rect, asize, a.contentMode); - rect = ASTextCGRectPixelRound(rect); - rect = CGRectStandardize(rect); - rect.origin.x += point.x + verticalOffset; - rect.origin.y += point.y; - if (image) { - CGImageRef ref = image.CGImage; - if (ref) { - CGContextSaveGState(context); - CGContextTranslateCTM(context, 0, CGRectGetMaxY(rect) + CGRectGetMinY(rect)); - CGContextScaleCTM(context, 1, -1); - CGContextDrawImage(context, rect, ref); - CGContextRestoreGState(context); - } - } else if (view) { - view.frame = rect; - [targetView addSubview:view]; - } else if (layer) { - layer.frame = rect; - [targetLayer addSublayer:layer]; - } - } -} - -static void ASTextDrawShadow(ASTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, BOOL (^cancel)(void)) { - //move out of context. (0xFFFF is just a random large number) - CGFloat offsetAlterX = size.width + 0xFFFF; - - BOOL isVertical = layout.container.verticalForm; - CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0; - - CGContextSaveGState(context); { - CGContextTranslateCTM(context, point.x, point.y); - CGContextTranslateCTM(context, 0, size.height); - CGContextScaleCTM(context, 1, -1); - NSArray *lines = layout.lines; - for (NSUInteger l = 0, lMax = layout.lines.count; l < lMax; l++) { - if (cancel && cancel()) break; - ASTextLine *line = lines[l]; - if (layout.truncatedLine && layout.truncatedLine.index == line.index) line = layout.truncatedLine; - NSArray *lineRunRanges = line.verticalRotateRange; - CGFloat linePosX = line.position.x; - CGFloat linePosY = size.height - line.position.y; - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSUInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - CGContextSetTextMatrix(context, CGAffineTransformIdentity); - CGContextSetTextPosition(context, linePosX, linePosY); - NSDictionary *attrs = (id)CTRunGetAttributes(run); - ASTextShadow *shadow = attrs[ASTextShadowAttributeName]; - ASTextShadow *nsShadow = [ASTextShadow shadowWithNSShadow:attrs[NSShadowAttributeName]]; // NSShadow compatible - if (nsShadow) { - nsShadow.subShadow = shadow; - shadow = nsShadow; - } - while (shadow) { - if (!shadow.color) { - shadow = shadow.subShadow; - continue; - } - CGSize offset = shadow.offset; - offset.width -= offsetAlterX; - CGContextSaveGState(context); { - CGContextSetShadowWithColor(context, offset, shadow.radius, shadow.color.CGColor); - CGContextSetBlendMode(context, shadow.blendMode); - CGContextTranslateCTM(context, offsetAlterX, 0); - ASTextDrawRun(line, run, context, size, isVertical, lineRunRanges[r], verticalOffset); - } CGContextRestoreGState(context); - shadow = shadow.subShadow; - } - } - } - } CGContextRestoreGState(context); -} - -static void ASTextDrawInnerShadow(ASTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, BOOL (^cancel)(void)) { - CGContextSaveGState(context); - CGContextTranslateCTM(context, point.x, point.y); - CGContextTranslateCTM(context, 0, size.height); - CGContextScaleCTM(context, 1, -1); - CGContextSetTextMatrix(context, CGAffineTransformIdentity); - - BOOL isVertical = layout.container.verticalForm; - CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0; - - NSArray *lines = layout.lines; - for (NSUInteger l = 0, lMax = lines.count; l < lMax; l++) { - if (cancel && cancel()) break; - - ASTextLine *line = lines[l]; - if (layout.truncatedLine && layout.truncatedLine.index == line.index) line = layout.truncatedLine; - NSArray *lineRunRanges = line.verticalRotateRange; - CGFloat linePosX = line.position.x; - CGFloat linePosY = size.height - line.position.y; - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSUInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - if (CTRunGetGlyphCount(run) == 0) continue; - CGContextSetTextMatrix(context, CGAffineTransformIdentity); - CGContextSetTextPosition(context, linePosX, linePosY); - NSDictionary *attrs = (id)CTRunGetAttributes(run); - ASTextShadow *shadow = attrs[ASTextInnerShadowAttributeName]; - while (shadow) { - if (!shadow.color) { - shadow = shadow.subShadow; - continue; - } - CGPoint runPosition = CGPointZero; - CTRunGetPositions(run, CFRangeMake(0, 1), &runPosition); - CGRect runImageBounds = CTRunGetImageBounds(run, context, CFRangeMake(0, 0)); - runImageBounds.origin.x += runPosition.x; - if (runImageBounds.size.width < 0.1 || runImageBounds.size.height < 0.1) continue; - - CFDictionaryRef runAttrs = CTRunGetAttributes(run); - NSValue *glyphTransformValue = (NSValue *)CFDictionaryGetValue(runAttrs, (__bridge const void *)(ASTextGlyphTransformAttributeName)); - if (glyphTransformValue) { - runImageBounds = CGRectMake(0, 0, size.width, size.height); - } - - // text inner shadow - CGContextSaveGState(context); { - CGContextSetBlendMode(context, shadow.blendMode); - CGContextSetShadowWithColor(context, CGSizeZero, 0, NULL); - CGContextSetAlpha(context, CGColorGetAlpha(shadow.color.CGColor)); - CGContextClipToRect(context, runImageBounds); - CGContextBeginTransparencyLayer(context, NULL); { - UIColor *opaqueShadowColor = [shadow.color colorWithAlphaComponent:1]; - CGContextSetShadowWithColor(context, shadow.offset, shadow.radius, opaqueShadowColor.CGColor); - CGContextSetFillColorWithColor(context, opaqueShadowColor.CGColor); - CGContextSetBlendMode(context, kCGBlendModeSourceOut); - CGContextBeginTransparencyLayer(context, NULL); { - CGContextFillRect(context, runImageBounds); - CGContextSetBlendMode(context, kCGBlendModeDestinationIn); - CGContextBeginTransparencyLayer(context, NULL); { - ASTextDrawRun(line, run, context, size, isVertical, lineRunRanges[r], verticalOffset); - } CGContextEndTransparencyLayer(context); - } CGContextEndTransparencyLayer(context); - } CGContextEndTransparencyLayer(context); - } CGContextRestoreGState(context); - shadow = shadow.subShadow; - } - } - } - - CGContextRestoreGState(context); -} - -static void ASTextDrawDebug(ASTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, ASTextDebugOption *op) { - UIGraphicsPushContext(context); - CGContextSaveGState(context); - CGContextTranslateCTM(context, point.x, point.y); - CGContextSetLineWidth(context, 1.0 / ASScreenScale()); - CGContextSetLineDash(context, 0, NULL, 0); - CGContextSetLineJoin(context, kCGLineJoinMiter); - CGContextSetLineCap(context, kCGLineCapButt); - - BOOL isVertical = layout.container.verticalForm; - CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0; - CGContextTranslateCTM(context, verticalOffset, 0); - - if (op.CTFrameBorderColor || op.CTFrameFillColor) { - UIBezierPath *path = layout.container.path; - if (!path) { - CGRect rect = (CGRect){CGPointZero, layout.container.size}; - rect = UIEdgeInsetsInsetRect(rect, layout.container.insets); - if (op.CTFrameBorderColor) rect = ASTextCGRectPixelHalf(rect); - else rect = ASTextCGRectPixelRound(rect); - path = [UIBezierPath bezierPathWithRect:rect]; - } - [path closePath]; - - for (UIBezierPath *ex in layout.container.exclusionPaths) { - [path appendPath:ex]; - } - if (op.CTFrameFillColor) { - [op.CTFrameFillColor setFill]; - if (layout.container.pathLineWidth > 0) { - CGContextSaveGState(context); { - CGContextBeginTransparencyLayer(context, NULL); { - CGContextAddPath(context, path.CGPath); - if (layout.container.pathFillEvenOdd) { - CGContextEOFillPath(context); - } else { - CGContextFillPath(context); - } - CGContextSetBlendMode(context, kCGBlendModeDestinationOut); - [[UIColor blackColor] setFill]; - CGPathRef cgPath = CGPathCreateCopyByStrokingPath(path.CGPath, NULL, layout.container.pathLineWidth, kCGLineCapButt, kCGLineJoinMiter, 0); - if (cgPath) { - CGContextAddPath(context, cgPath); - CGContextFillPath(context); - } - CGPathRelease(cgPath); - } CGContextEndTransparencyLayer(context); - } CGContextRestoreGState(context); - } else { - CGContextAddPath(context, path.CGPath); - if (layout.container.pathFillEvenOdd) { - CGContextEOFillPath(context); - } else { - CGContextFillPath(context); - } - } - } - if (op.CTFrameBorderColor) { - CGContextSaveGState(context); { - if (layout.container.pathLineWidth > 0) { - CGContextSetLineWidth(context, layout.container.pathLineWidth); - } - [op.CTFrameBorderColor setStroke]; - CGContextAddPath(context, path.CGPath); - CGContextStrokePath(context); - } CGContextRestoreGState(context); - } - } - - NSArray *lines = layout.lines; - for (NSUInteger l = 0, lMax = lines.count; l < lMax; l++) { - ASTextLine *line = lines[l]; - if (layout.truncatedLine && layout.truncatedLine.index == line.index) line = layout.truncatedLine; - CGRect lineBounds = line.bounds; - if (op.CTLineFillColor) { - [op.CTLineFillColor setFill]; - CGContextAddRect(context, ASTextCGRectPixelRound(lineBounds)); - CGContextFillPath(context); - } - if (op.CTLineBorderColor) { - [op.CTLineBorderColor setStroke]; - CGContextAddRect(context, ASTextCGRectPixelHalf(lineBounds)); - CGContextStrokePath(context); - } - if (op.baselineColor) { - [op.baselineColor setStroke]; - if (isVertical) { - CGFloat x = ASTextCGFloatPixelHalf(line.position.x); - CGFloat y1 = ASTextCGFloatPixelHalf(line.top); - CGFloat y2 = ASTextCGFloatPixelHalf(line.bottom); - CGContextMoveToPoint(context, x, y1); - CGContextAddLineToPoint(context, x, y2); - CGContextStrokePath(context); - } else { - CGFloat x1 = ASTextCGFloatPixelHalf(lineBounds.origin.x); - CGFloat x2 = ASTextCGFloatPixelHalf(lineBounds.origin.x + lineBounds.size.width); - CGFloat y = ASTextCGFloatPixelHalf(line.position.y); - CGContextMoveToPoint(context, x1, y); - CGContextAddLineToPoint(context, x2, y); - CGContextStrokePath(context); - } - } - if (op.CTLineNumberColor) { - [op.CTLineNumberColor set]; - NSMutableAttributedString *num = [[NSMutableAttributedString alloc] initWithString:@(l).description]; - num.as_color = op.CTLineNumberColor; - num.as_font = [UIFont systemFontOfSize:6]; - [num drawAtPoint:CGPointMake(line.position.x, line.position.y - (isVertical ? 1 : 6))]; - } - if (op.CTRunFillColor || op.CTRunBorderColor || op.CTRunNumberColor || op.CGGlyphFillColor || op.CGGlyphBorderColor) { - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSUInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - CFIndex glyphCount = CTRunGetGlyphCount(run); - if (glyphCount == 0) continue; - - CGPoint glyphPositions[glyphCount]; - CTRunGetPositions(run, CFRangeMake(0, glyphCount), glyphPositions); - - CGSize glyphAdvances[glyphCount]; - CTRunGetAdvances(run, CFRangeMake(0, glyphCount), glyphAdvances); - - CGPoint runPosition = glyphPositions[0]; - if (isVertical) { - ASTEXT_SWAP(runPosition.x, runPosition.y); - runPosition.x = line.position.x; - runPosition.y += line.position.y; - } else { - runPosition.x += line.position.x; - runPosition.y = line.position.y - runPosition.y; - } - - CGFloat ascent, descent, leading; - CGFloat width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, &leading); - CGRect runTypoBounds; - if (isVertical) { - runTypoBounds = CGRectMake(runPosition.x - descent, runPosition.y, ascent + descent, width); - } else { - runTypoBounds = CGRectMake(runPosition.x, line.position.y - ascent, width, ascent + descent); - } - - if (op.CTRunFillColor) { - [op.CTRunFillColor setFill]; - CGContextAddRect(context, ASTextCGRectPixelRound(runTypoBounds)); - CGContextFillPath(context); - } - if (op.CTRunBorderColor) { - [op.CTRunBorderColor setStroke]; - CGContextAddRect(context, ASTextCGRectPixelHalf(runTypoBounds)); - CGContextStrokePath(context); - } - if (op.CTRunNumberColor) { - [op.CTRunNumberColor set]; - NSMutableAttributedString *num = [[NSMutableAttributedString alloc] initWithString:@(r).description]; - num.as_color = op.CTRunNumberColor; - num.as_font = [UIFont systemFontOfSize:6]; - [num drawAtPoint:CGPointMake(runTypoBounds.origin.x, runTypoBounds.origin.y - 1)]; - } - if (op.CGGlyphBorderColor || op.CGGlyphFillColor) { - for (NSUInteger g = 0; g < glyphCount; g++) { - CGPoint pos = glyphPositions[g]; - CGSize adv = glyphAdvances[g]; - CGRect rect; - if (isVertical) { - ASTEXT_SWAP(pos.x, pos.y); - pos.x = runPosition.x; - pos.y += line.position.y; - rect = CGRectMake(pos.x - descent, pos.y, runTypoBounds.size.width, adv.width); - } else { - pos.x += line.position.x; - pos.y = runPosition.y; - rect = CGRectMake(pos.x, pos.y - ascent, adv.width, runTypoBounds.size.height); - } - if (op.CGGlyphFillColor) { - [op.CGGlyphFillColor setFill]; - CGContextAddRect(context, ASTextCGRectPixelRound(rect)); - CGContextFillPath(context); - } - if (op.CGGlyphBorderColor) { - [op.CGGlyphBorderColor setStroke]; - CGContextAddRect(context, ASTextCGRectPixelHalf(rect)); - CGContextStrokePath(context); - } - } - } - } - } - } - CGContextRestoreGState(context); - UIGraphicsPopContext(); -} - - -- (void)drawInContext:(CGContextRef)context - size:(CGSize)size - point:(CGPoint)point - view:(UIView *)view - layer:(CALayer *)layer - debug:(ASTextDebugOption *)debug - cancel:(BOOL (^)(void))cancel{ - @autoreleasepool { - if (self.needDrawBlockBorder && context) { - if (cancel && cancel()) return; - ASTextDrawBlockBorder(self, context, size, point, cancel); - } - if (self.needDrawBackgroundBorder && context) { - if (cancel && cancel()) return; - ASTextDrawBorder(self, context, size, point, ASTextBorderTypeBackgound, cancel); - } - if (self.needDrawShadow && context) { - if (cancel && cancel()) return; - ASTextDrawShadow(self, context, size, point, cancel); - } - if (self.needDrawUnderline && context) { - if (cancel && cancel()) return; - ASTextDrawDecoration(self, context, size, point, ASTextDecorationTypeUnderline, cancel); - } - if (self.needDrawText && context) { - if (cancel && cancel()) return; - ASTextDrawText(self, context, size, point, cancel); - } - if (self.needDrawAttachment && (context || view || layer)) { - if (cancel && cancel()) return; - ASTextDrawAttachment(self, context, size, point, view, layer, cancel); - } - if (self.needDrawInnerShadow && context) { - if (cancel && cancel()) return; - ASTextDrawInnerShadow(self, context, size, point, cancel); - } - if (self.needDrawStrikethrough && context) { - if (cancel && cancel()) return; - ASTextDrawDecoration(self, context, size, point, ASTextDecorationTypeStrikethrough, cancel); - } - if (self.needDrawBorder && context) { - if (cancel && cancel()) return; - ASTextDrawBorder(self, context, size, point, ASTextBorderTypeNormal, cancel); - } - if (debug.needDrawDebug && context) { - if (cancel && cancel()) return; - ASTextDrawDebug(self, context, size, point, debug); - } - } -} - -- (void)drawInContext:(CGContextRef)context - size:(CGSize)size - debug:(ASTextDebugOption *)debug { - [self drawInContext:context size:size point:CGPointZero view:nil layer:nil debug:debug cancel:nil]; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Component/ASTextLine.h b/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Component/ASTextLine.h deleted file mode 100644 index acb02e991b..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Component/ASTextLine.h +++ /dev/null @@ -1,77 +0,0 @@ -// -// ASTextLine.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - -@class ASTextRunGlyphRange; - -NS_ASSUME_NONNULL_BEGIN - -/** - A text line object wrapped `CTLineRef`, see `ASTextLayout` for more. - */ -@interface ASTextLine : NSObject - -+ (instancetype)lineWithCTLine:(CTLineRef)CTLine position:(CGPoint)position vertical:(BOOL)isVertical NS_RETURNS_RETAINED; - -@property (nonatomic) NSUInteger index; ///< line index -@property (nonatomic) NSUInteger row; ///< line row -@property (nullable, nonatomic) NSArray *> *verticalRotateRange; ///< Run rotate range - -@property (nonatomic, readonly) CTLineRef CTLine; ///< CoreText line -@property (nonatomic, readonly) NSRange range; ///< string range -@property (nonatomic, readonly) BOOL vertical; ///< vertical form - -@property (nonatomic, readonly) CGRect bounds; ///< bounds (ascent + descent) -@property (nonatomic, readonly) CGSize size; ///< bounds.size -@property (nonatomic, readonly) CGFloat width; ///< bounds.size.width -@property (nonatomic, readonly) CGFloat height; ///< bounds.size.height -@property (nonatomic, readonly) CGFloat top; ///< bounds.origin.y -@property (nonatomic, readonly) CGFloat bottom; ///< bounds.origin.y + bounds.size.height -@property (nonatomic, readonly) CGFloat left; ///< bounds.origin.x -@property (nonatomic, readonly) CGFloat right; ///< bounds.origin.x + bounds.size.width - -@property (nonatomic) CGPoint position; ///< baseline position -@property (nonatomic, readonly) CGFloat ascent; ///< line ascent -@property (nonatomic, readonly) CGFloat descent; ///< line descent -@property (nonatomic, readonly) CGFloat leading; ///< line leading -@property (nonatomic, readonly) CGFloat lineWidth; ///< line width -@property (nonatomic, readonly) CGFloat trailingWhitespaceWidth; - -@property (nullable, nonatomic, readonly) NSArray *attachments; ///< ASTextAttachment -@property (nullable, nonatomic, readonly) NSArray *attachmentRanges; ///< NSRange(NSValue) -@property (nullable, nonatomic, readonly) NSArray *attachmentRects; ///< CGRect(NSValue) - -@end - - -typedef NS_ENUM(NSUInteger, ASTextRunGlyphDrawMode) { - /// No rotate. - ASTextRunGlyphDrawModeHorizontal = 0, - - /// Rotate vertical for single glyph. - ASTextRunGlyphDrawModeVerticalRotate = 1, - - /// Rotate vertical for single glyph, and move the glyph to a better position, - /// such as fullwidth punctuation. - ASTextRunGlyphDrawModeVerticalRotateMove = 2, -}; - -/** - A range in CTRun, used for vertical form. - */ -@interface ASTextRunGlyphRange : NSObject -@property (nonatomic) NSRange glyphRangeInRun; -@property (nonatomic) ASTextRunGlyphDrawMode drawMode; -+ (instancetype)rangeWithRange:(NSRange)range drawMode:(ASTextRunGlyphDrawMode)mode NS_RETURNS_RETAINED; -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Component/ASTextLine.mm b/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Component/ASTextLine.mm deleted file mode 100644 index a01311d83c..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Component/ASTextLine.mm +++ /dev/null @@ -1,164 +0,0 @@ -// -// ASTextLine.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -@implementation ASTextLine { - CGFloat _firstGlyphPos; // first glyph position for baseline, typically 0. -} - -+ (instancetype)lineWithCTLine:(CTLineRef)CTLine position:(CGPoint)position vertical:(BOOL)isVertical NS_RETURNS_RETAINED { - if (!CTLine) return nil; - ASTextLine *line = [self new]; - line->_position = position; - line->_vertical = isVertical; - [line setCTLine:CTLine]; - return line; -} - -- (void)dealloc { - if (_CTLine) CFRelease(_CTLine); -} - -- (void)setCTLine:(_Nonnull CTLineRef)CTLine { - if (_CTLine != CTLine) { - if (CTLine) CFRetain(CTLine); - if (_CTLine) CFRelease(_CTLine); - _CTLine = CTLine; - if (_CTLine) { - _lineWidth = CTLineGetTypographicBounds(_CTLine, &_ascent, &_descent, &_leading); - CFRange range = CTLineGetStringRange(_CTLine); - _range = NSMakeRange(range.location, range.length); - if (CTLineGetGlyphCount(_CTLine) > 0) { - CFArrayRef runs = CTLineGetGlyphRuns(_CTLine); - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, 0); - CGPoint pos; - CTRunGetPositions(run, CFRangeMake(0, 1), &pos); - _firstGlyphPos = pos.x; - } else { - _firstGlyphPos = 0; - } - _trailingWhitespaceWidth = CTLineGetTrailingWhitespaceWidth(_CTLine); - } else { - _lineWidth = _ascent = _descent = _leading = _firstGlyphPos = _trailingWhitespaceWidth = 0; - _range = NSMakeRange(0, 0); - } - [self reloadBounds]; - } -} - -- (void)setPosition:(CGPoint)position { - _position = position; - [self reloadBounds]; -} - -- (void)reloadBounds { - if (_vertical) { - _bounds = CGRectMake(_position.x - _descent, _position.y, _ascent + _descent, _lineWidth); - _bounds.origin.y += _firstGlyphPos; - } else { - _bounds = CGRectMake(_position.x, _position.y - _ascent, _lineWidth, _ascent + _descent); - _bounds.origin.x += _firstGlyphPos; - } - - _attachments = nil; - _attachmentRanges = nil; - _attachmentRects = nil; - if (!_CTLine) return; - CFArrayRef runs = CTLineGetGlyphRuns(_CTLine); - NSUInteger runCount = CFArrayGetCount(runs); - if (runCount == 0) return; - - NSMutableArray *attachments = [NSMutableArray new]; - NSMutableArray *attachmentRanges = [NSMutableArray new]; - NSMutableArray *attachmentRects = [NSMutableArray new]; - for (NSUInteger r = 0; r < runCount; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - CFIndex glyphCount = CTRunGetGlyphCount(run); - if (glyphCount == 0) continue; - NSDictionary *attrs = (id)CTRunGetAttributes(run); - ASTextAttachment *attachment = attrs[ASTextAttachmentAttributeName]; - if (attachment) { - CGPoint runPosition = CGPointZero; - CTRunGetPositions(run, CFRangeMake(0, 1), &runPosition); - - CGFloat ascent, descent, leading, runWidth; - CGRect runTypoBounds; - runWidth = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, &leading); - - if (_vertical) { - ASTEXT_SWAP(runPosition.x, runPosition.y); - runPosition.y = _position.y + runPosition.y; - runTypoBounds = CGRectMake(_position.x + runPosition.x - descent, runPosition.y , ascent + descent, runWidth); - } else { - runPosition.x += _position.x; - runPosition.y = _position.y - runPosition.y; - runTypoBounds = CGRectMake(runPosition.x, runPosition.y - ascent, runWidth, ascent + descent); - } - - NSRange runRange = ASTextNSRangeFromCFRange(CTRunGetStringRange(run)); - [attachments addObject:attachment]; - [attachmentRanges addObject:[NSValue valueWithRange:runRange]]; - [attachmentRects addObject:[NSValue valueWithCGRect:runTypoBounds]]; - } - } - _attachments = attachments.count ? attachments : nil; - _attachmentRanges = attachmentRanges.count ? attachmentRanges : nil; - _attachmentRects = attachmentRects.count ? attachmentRects : nil; -} - -- (CGSize)size { - return _bounds.size; -} - -- (CGFloat)width { - return CGRectGetWidth(_bounds); -} - -- (CGFloat)height { - return CGRectGetHeight(_bounds); -} - -- (CGFloat)top { - return CGRectGetMinY(_bounds); -} - -- (CGFloat)bottom { - return CGRectGetMaxY(_bounds); -} - -- (CGFloat)left { - return CGRectGetMinX(_bounds); -} - -- (CGFloat)right { - return CGRectGetMaxX(_bounds); -} - -- (NSString *)description { - NSMutableString *desc = @"".mutableCopy; - NSRange range = self.range; - [desc appendFormat:@" row:%ld range:%tu,%tu", self, (long)self.row, range.location, range.length]; - [desc appendFormat:@" position:%@",NSStringFromCGPoint(self.position)]; - [desc appendFormat:@" bounds:%@",NSStringFromCGRect(self.bounds)]; - return desc; -} - -@end - - -@implementation ASTextRunGlyphRange -+ (instancetype)rangeWithRange:(NSRange)range drawMode:(ASTextRunGlyphDrawMode)mode NS_RETURNS_RETAINED { - ASTextRunGlyphRange *one = [self new]; - one.glyphRangeInRun = range; - one.drawMode = mode; - return one; -} -@end diff --git a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/String/ASTextAttribute.h b/submodules/AsyncDisplayKit/Source/Private/TextExperiment/String/ASTextAttribute.h deleted file mode 100644 index 2d9e3771a0..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/String/ASTextAttribute.h +++ /dev/null @@ -1,346 +0,0 @@ -// -// ASTextAttribute.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -#pragma mark - Enum Define - -/// The attribute type -typedef NS_OPTIONS(NSInteger, ASTextAttributeType) { - ASTextAttributeTypeNone = 0, - ASTextAttributeTypeUIKit = 1 << 0, ///< UIKit attributes, such as UILabel/UITextField/drawInRect. - ASTextAttributeTypeCoreText = 1 << 1, ///< CoreText attributes, used by CoreText. - ASTextAttributeTypeASText = 1 << 2, ///< ASText attributes, used by ASText. -}; - -/// Get the attribute type from an attribute name. -AS_EXTERN ASTextAttributeType ASTextAttributeGetType(NSString *attributeName); - -/** - Line style in ASText (similar to NSUnderlineStyle). - */ -typedef NS_OPTIONS (NSInteger, ASTextLineStyle) { - // basic style (bitmask:0xFF) - ASTextLineStyleNone = 0x00, ///< ( ) Do not draw a line (Default). - ASTextLineStyleSingle = 0x01, ///< (──────) Draw a single line. - ASTextLineStyleThick = 0x02, ///< (━━━━━━━) Draw a thick line. - ASTextLineStyleDouble = 0x09, ///< (══════) Draw a double line. - - // style pattern (bitmask:0xF00) - ASTextLineStylePatternSolid = 0x000, ///< (────────) Draw a solid line (Default). - ASTextLineStylePatternDot = 0x100, ///< (‑ ‑ ‑ ‑ ‑ ‑) Draw a line of dots. - ASTextLineStylePatternDash = 0x200, ///< (— — — —) Draw a line of dashes. - ASTextLineStylePatternDashDot = 0x300, ///< (— ‑ — ‑ — ‑) Draw a line of alternating dashes and dots. - ASTextLineStylePatternDashDotDot = 0x400, ///< (— ‑ ‑ — ‑ ‑) Draw a line of alternating dashes and two dots. - ASTextLineStylePatternCircleDot = 0x900, ///< (••••••••••••) Draw a line of small circle dots. -}; - -/** - Text vertical alignment. - */ -typedef NS_ENUM(NSInteger, ASTextVerticalAlignment) { - ASTextVerticalAlignmentTop = 0, ///< Top alignment. - ASTextVerticalAlignmentCenter = 1, ///< Center alignment. - ASTextVerticalAlignmentBottom = 2, ///< Bottom alignment. -}; - -/** - The direction define in ASText. - */ -typedef NS_OPTIONS(NSUInteger, ASTextDirection) { - ASTextDirectionNone = 0, - ASTextDirectionTop = 1 << 0, - ASTextDirectionRight = 1 << 1, - ASTextDirectionBottom = 1 << 2, - ASTextDirectionLeft = 1 << 3, -}; - -/** - The trunction type, tells the truncation engine which type of truncation is being requested. - */ -typedef NS_ENUM (NSUInteger, ASTextTruncationType) { - /// No truncate. - ASTextTruncationTypeNone = 0, - - /// Truncate at the beginning of the line, leaving the end portion visible. - ASTextTruncationTypeStart = 1, - - /// Truncate at the end of the line, leaving the start portion visible. - ASTextTruncationTypeEnd = 2, - - /// Truncate in the middle of the line, leaving both the start and the end portions visible. - ASTextTruncationTypeMiddle = 3, -}; - - - -#pragma mark - Attribute Name Defined in ASText - -/// The value of this attribute is a `ASTextBackedString` object. -/// Use this attribute to store the original plain text if it is replaced by something else (such as attachment). -UIKIT_EXTERN NSString *const ASTextBackedStringAttributeName; - -/// The value of this attribute is a `ASTextBinding` object. -/// Use this attribute to bind a range of text together, as if it was a single charactor. -UIKIT_EXTERN NSString *const ASTextBindingAttributeName; - -/// The value of this attribute is a `ASTextShadow` object. -/// Use this attribute to add shadow to a range of text. -/// Shadow will be drawn below text glyphs. Use ASTextShadow.subShadow to add multi-shadow. -UIKIT_EXTERN NSString *const ASTextShadowAttributeName; - -/// The value of this attribute is a `ASTextShadow` object. -/// Use this attribute to add inner shadow to a range of text. -/// Inner shadow will be drawn above text glyphs. Use ASTextShadow.subShadow to add multi-shadow. -UIKIT_EXTERN NSString *const ASTextInnerShadowAttributeName; - -/// The value of this attribute is a `ASTextDecoration` object. -/// Use this attribute to add underline to a range of text. -/// The underline will be drawn below text glyphs. -UIKIT_EXTERN NSString *const ASTextUnderlineAttributeName; - -/// The value of this attribute is a `ASTextDecoration` object. -/// Use this attribute to add strikethrough (delete line) to a range of text. -/// The strikethrough will be drawn above text glyphs. -UIKIT_EXTERN NSString *const ASTextStrikethroughAttributeName; - -/// The value of this attribute is a `ASTextBorder` object. -/// Use this attribute to add cover border or cover color to a range of text. -/// The border will be drawn above the text glyphs. -UIKIT_EXTERN NSString *const ASTextBorderAttributeName; - -/// The value of this attribute is a `ASTextBorder` object. -/// Use this attribute to add background border or background color to a range of text. -/// The border will be drawn below the text glyphs. -UIKIT_EXTERN NSString *const ASTextBackgroundBorderAttributeName; - -/// The value of this attribute is a `ASTextBorder` object. -/// Use this attribute to add a code block border to one or more line of text. -/// The border will be drawn below the text glyphs. -UIKIT_EXTERN NSString *const ASTextBlockBorderAttributeName; - -/// The value of this attribute is a `ASTextAttachment` object. -/// Use this attribute to add attachment to text. -/// It should be used in conjunction with a CTRunDelegate. -UIKIT_EXTERN NSString *const ASTextAttachmentAttributeName; - -/// The value of this attribute is a `ASTextHighlight` object. -/// Use this attribute to add a touchable highlight state to a range of text. -UIKIT_EXTERN NSString *const ASTextHighlightAttributeName; - -/// The value of this attribute is a `NSValue` object stores CGAffineTransform. -/// Use this attribute to add transform to each glyph in a range of text. -UIKIT_EXTERN NSString *const ASTextGlyphTransformAttributeName; - - - -#pragma mark - String Token Define - -UIKIT_EXTERN NSString *const ASTextAttachmentToken; ///< Object replacement character (U+FFFC), used for text attachment. -UIKIT_EXTERN NSString *const ASTextTruncationToken; ///< Horizontal ellipsis (U+2026), used for text truncation "…". - - - -#pragma mark - Attribute Value Define - -/** - The tap/long press action callback defined in ASText. - - @param containerView The text container view (such as ASLabel/ASTextView). - @param text The whole text. - @param range The text range in `text` (if no range, the range.location is NSNotFound). - @param rect The text frame in `containerView` (if no data, the rect is CGRectNull). - */ -typedef void(^ASTextAction)(UIView *containerView, NSAttributedString *text, NSRange range, CGRect rect); - - -/** - ASTextBackedString objects are used by the NSAttributedString class cluster - as the values for text backed string attributes (stored in the attributed - string under the key named ASTextBackedStringAttributeName). - - It may used for copy/paste plain text from attributed string. - Example: If :) is replace by a custom emoji (such as😊), the backed string can be set to @":)". - */ -@interface ASTextBackedString : NSObject -+ (instancetype)stringWithString:(nullable NSString *)string NS_RETURNS_RETAINED; -@property (nullable, nonatomic, copy) NSString *string; ///< backed string -@end - - -/** - ASTextBinding objects are used by the NSAttributedString class cluster - as the values for shadow attributes (stored in the attributed string under - the key named ASTextBindingAttributeName). - - Add this to a range of text will make the specified characters 'binding together'. - ASTextView will treat the range of text as a single character during text - selection and edit. - */ -@interface ASTextBinding : NSObject -+ (instancetype)bindingWithDeleteConfirm:(BOOL)deleteConfirm NS_RETURNS_RETAINED; -@property (nonatomic) BOOL deleteConfirm; ///< confirm the range when delete in ASTextView -@end - - -/** - ASTextShadow objects are used by the NSAttributedString class cluster - as the values for shadow attributes (stored in the attributed string under - the key named ASTextShadowAttributeName or ASTextInnerShadowAttributeName). - - It's similar to `NSShadow`, but offers more options. - */ -@interface ASTextShadow : NSObject -+ (instancetype)shadowWithColor:(nullable UIColor *)color offset:(CGSize)offset radius:(CGFloat)radius NS_RETURNS_RETAINED; - -@property (nullable, nonatomic) UIColor *color; ///< shadow color -@property (nonatomic) CGSize offset; ///< shadow offset -@property (nonatomic) CGFloat radius; ///< shadow blur radius -@property (nonatomic) CGBlendMode blendMode; ///< shadow blend mode -@property (nullable, nonatomic) ASTextShadow *subShadow; ///< a sub shadow which will be added above the parent shadow - -+ (instancetype)shadowWithNSShadow:(NSShadow *)nsShadow NS_RETURNS_RETAINED; ///< convert NSShadow to ASTextShadow -- (NSShadow *)nsShadow; ///< convert ASTextShadow to NSShadow -@end - - -/** - ASTextDecorationLine objects are used by the NSAttributedString class cluster - as the values for decoration line attributes (stored in the attributed string under - the key named ASTextUnderlineAttributeName or ASTextStrikethroughAttributeName). - - When it's used as underline, the line is drawn below text glyphs; - when it's used as strikethrough, the line is drawn above text glyphs. - */ -@interface ASTextDecoration : NSObject -+ (instancetype)decorationWithStyle:(ASTextLineStyle)style NS_RETURNS_RETAINED; -+ (instancetype)decorationWithStyle:(ASTextLineStyle)style width:(nullable NSNumber *)width color:(nullable UIColor *)color NS_RETURNS_RETAINED; -@property (nonatomic) ASTextLineStyle style; ///< line style -@property (nullable, nonatomic) NSNumber *width; ///< line width (nil means automatic width) -@property (nullable, nonatomic) UIColor *color; ///< line color (nil means automatic color) -@property (nullable, nonatomic) ASTextShadow *shadow; ///< line shadow -@end - - -/** - ASTextBorder objects are used by the NSAttributedString class cluster - as the values for border attributes (stored in the attributed string under - the key named ASTextBorderAttributeName or ASTextBackgroundBorderAttributeName). - - It can be used to draw a border around a range of text, or draw a background - to a range of text. - - Example: - ╭──────╮ - │ Text │ - ╰──────╯ - */ -@interface ASTextBorder : NSObject -+ (instancetype)borderWithLineStyle:(ASTextLineStyle)lineStyle lineWidth:(CGFloat)width strokeColor:(nullable UIColor *)color NS_RETURNS_RETAINED; -+ (instancetype)borderWithFillColor:(nullable UIColor *)color cornerRadius:(CGFloat)cornerRadius NS_RETURNS_RETAINED; -@property (nonatomic) ASTextLineStyle lineStyle; ///< border line style -@property (nonatomic) CGFloat strokeWidth; ///< border line width -@property (nullable, nonatomic) UIColor *strokeColor; ///< border line color -@property (nonatomic) CGLineJoin lineJoin; ///< border line join -@property (nonatomic) UIEdgeInsets insets; ///< border insets for text bounds -@property (nonatomic) CGFloat cornerRadius; ///< border corder radius -@property (nullable, nonatomic) ASTextShadow *shadow; ///< border shadow -@property (nullable, nonatomic) UIColor *fillColor; ///< inner fill color -@end - - -/** - ASTextAttachment objects are used by the NSAttributedString class cluster - as the values for attachment attributes (stored in the attributed string under - the key named ASTextAttachmentAttributeName). - - When display an attributed string which contains `ASTextAttachment` object, - the content will be placed in text metric. If the content is `UIImage`, - then it will be drawn to CGContext; if the content is `UIView` or `CALayer`, - then it will be added to the text container's view or layer. - */ -@interface ASTextAttachment : NSObject -+ (instancetype)attachmentWithContent:(nullable id)content NS_RETURNS_RETAINED; -@property (nullable, nonatomic) id content; ///< Supported type: UIImage, UIView, CALayer -@property (nonatomic) UIViewContentMode contentMode; ///< Content display mode. -@property (nonatomic) UIEdgeInsets contentInsets; ///< The insets when drawing content. -@property (nullable, nonatomic) NSDictionary *userInfo; ///< The user information dictionary. -@end - - -/** - ASTextHighlight objects are used by the NSAttributedString class cluster - as the values for touchable highlight attributes (stored in the attributed string - under the key named ASTextHighlightAttributeName). - - When display an attributed string in `ASLabel` or `ASTextView`, the range of - highlight text can be toucheds down by users. If a range of text is turned into - highlighted state, the `attributes` in `ASTextHighlight` will be used to modify - (set or remove) the original attributes in the range for display. - */ -@interface ASTextHighlight : NSObject - -/** - Attributes that you can apply to text in an attributed string when highlight. - Key: Same as CoreText/ASText Attribute Name. - Value: Modify attribute value when highlight (NSNull for remove attribute). - */ -@property (nullable, nonatomic, copy) NSDictionary *attributes; - -/** - Creates a highlight object with specified attributes. - - @param attributes The attributes which will replace original attributes when highlight, - If the value is NSNull, it will removed when highlight. - */ -+ (instancetype)highlightWithAttributes:(nullable NSDictionary *)attributes NS_RETURNS_RETAINED; - -/** - Convenience methods to create a default highlight with the specifeid background color. - - @param color The background border color. - */ -+ (instancetype)highlightWithBackgroundColor:(nullable UIColor *)color NS_RETURNS_RETAINED; - -// Convenience methods below to set the `attributes`. -- (void)setFont:(nullable UIFont *)font; -- (void)setColor:(nullable UIColor *)color; -- (void)setStrokeWidth:(nullable NSNumber *)width; -- (void)setStrokeColor:(nullable UIColor *)color; -- (void)setShadow:(nullable ASTextShadow *)shadow; -- (void)setInnerShadow:(nullable ASTextShadow *)shadow; -- (void)setUnderline:(nullable ASTextDecoration *)underline; -- (void)setStrikethrough:(nullable ASTextDecoration *)strikethrough; -- (void)setBackgroundBorder:(nullable ASTextBorder *)border; -- (void)setBorder:(nullable ASTextBorder *)border; -- (void)setAttachment:(nullable ASTextAttachment *)attachment; - -/** - The user information dictionary, default is nil. - */ -@property (nullable, nonatomic, copy) NSDictionary *userInfo; - -/** - Tap action when user tap the highlight, default is nil. - If the value is nil, ASTextView or ASLabel will ask it's delegate to handle the tap action. - */ -@property (nullable, nonatomic) ASTextAction tapAction; - -/** - Long press action when user long press the highlight, default is nil. - If the value is nil, ASTextView or ASLabel will ask it's delegate to handle the long press action. - */ -@property (nullable, nonatomic) ASTextAction longPressAction; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/String/ASTextAttribute.mm b/submodules/AsyncDisplayKit/Source/Private/TextExperiment/String/ASTextAttribute.mm deleted file mode 100644 index d1abadbe1f..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/String/ASTextAttribute.mm +++ /dev/null @@ -1,488 +0,0 @@ -// -// ASTextAttribute.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASTextAttribute.h" -#import -#import -#import - -NSString *const ASTextBackedStringAttributeName = @"ASTextBackedString"; -NSString *const ASTextBindingAttributeName = @"ASTextBinding"; -NSString *const ASTextShadowAttributeName = @"ASTextShadow"; -NSString *const ASTextInnerShadowAttributeName = @"ASTextInnerShadow"; -NSString *const ASTextUnderlineAttributeName = @"ASTextUnderline"; -NSString *const ASTextStrikethroughAttributeName = @"ASTextStrikethrough"; -NSString *const ASTextBorderAttributeName = @"ASTextBorder"; -NSString *const ASTextBackgroundBorderAttributeName = @"ASTextBackgroundBorder"; -NSString *const ASTextBlockBorderAttributeName = @"ASTextBlockBorder"; -NSString *const ASTextAttachmentAttributeName = @"ASTextAttachment"; -NSString *const ASTextHighlightAttributeName = @"ASTextHighlight"; -NSString *const ASTextGlyphTransformAttributeName = @"ASTextGlyphTransform"; - -NSString *const ASTextAttachmentToken = @"\uFFFC"; -NSString *const ASTextTruncationToken = @"\u2026"; - - -ASTextAttributeType ASTextAttributeGetType(NSString *name){ - if (name.length == 0) return ASTextAttributeTypeNone; - - static NSMutableDictionary *dic; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - dic = [NSMutableDictionary new]; - NSNumber *All = @(ASTextAttributeTypeUIKit | ASTextAttributeTypeCoreText | ASTextAttributeTypeASText); - NSNumber *CoreText_ASText = @(ASTextAttributeTypeCoreText | ASTextAttributeTypeASText); - NSNumber *UIKit_ASText = @(ASTextAttributeTypeUIKit | ASTextAttributeTypeASText); - NSNumber *UIKit_CoreText = @(ASTextAttributeTypeUIKit | ASTextAttributeTypeCoreText); - NSNumber *UIKit = @(ASTextAttributeTypeUIKit); - NSNumber *CoreText = @(ASTextAttributeTypeCoreText); - NSNumber *ASText = @(ASTextAttributeTypeASText); - - dic[NSFontAttributeName] = All; - dic[NSKernAttributeName] = All; - dic[NSForegroundColorAttributeName] = UIKit; - dic[(id)kCTForegroundColorAttributeName] = CoreText; - dic[(id)kCTForegroundColorFromContextAttributeName] = CoreText; - dic[NSBackgroundColorAttributeName] = UIKit; - dic[NSStrokeWidthAttributeName] = All; - dic[NSStrokeColorAttributeName] = UIKit; - dic[(id)kCTStrokeColorAttributeName] = CoreText_ASText; - dic[NSShadowAttributeName] = UIKit_ASText; - dic[NSStrikethroughStyleAttributeName] = UIKit; - dic[NSUnderlineStyleAttributeName] = UIKit_CoreText; - dic[(id)kCTUnderlineColorAttributeName] = CoreText; - dic[NSLigatureAttributeName] = All; - dic[(id)kCTSuperscriptAttributeName] = UIKit; //it's a CoreText attrubite, but only supported by UIKit... - dic[NSVerticalGlyphFormAttributeName] = All; - dic[(id)kCTGlyphInfoAttributeName] = CoreText_ASText; -#if TARGET_OS_IOS -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - dic[(id)kCTCharacterShapeAttributeName] = CoreText_ASText; -#pragma clang diagnostic pop -#endif - dic[(id)kCTRunDelegateAttributeName] = CoreText_ASText; - dic[(id)kCTBaselineClassAttributeName] = CoreText_ASText; - dic[(id)kCTBaselineInfoAttributeName] = CoreText_ASText; - dic[(id)kCTBaselineReferenceInfoAttributeName] = CoreText_ASText; - dic[(id)kCTWritingDirectionAttributeName] = CoreText_ASText; - dic[NSParagraphStyleAttributeName] = All; - - dic[NSStrikethroughColorAttributeName] = UIKit; - dic[NSUnderlineColorAttributeName] = UIKit; - dic[NSTextEffectAttributeName] = UIKit; - dic[NSObliquenessAttributeName] = UIKit; - dic[NSExpansionAttributeName] = UIKit; - dic[(id)kCTLanguageAttributeName] = CoreText_ASText; - dic[NSBaselineOffsetAttributeName] = UIKit; - dic[NSWritingDirectionAttributeName] = All; - dic[NSAttachmentAttributeName] = UIKit; - dic[NSLinkAttributeName] = UIKit; - dic[(id)kCTRubyAnnotationAttributeName] = CoreText; - - dic[ASTextBackedStringAttributeName] = ASText; - dic[ASTextBindingAttributeName] = ASText; - dic[ASTextShadowAttributeName] = ASText; - dic[ASTextInnerShadowAttributeName] = ASText; - dic[ASTextUnderlineAttributeName] = ASText; - dic[ASTextStrikethroughAttributeName] = ASText; - dic[ASTextBorderAttributeName] = ASText; - dic[ASTextBackgroundBorderAttributeName] = ASText; - dic[ASTextBlockBorderAttributeName] = ASText; - dic[ASTextAttachmentAttributeName] = ASText; - dic[ASTextHighlightAttributeName] = ASText; - dic[ASTextGlyphTransformAttributeName] = ASText; - }); - NSNumber *num = dic[name]; - if (num) return num.integerValue; - return ASTextAttributeTypeNone; -} - - -@implementation ASTextBackedString - -+ (instancetype)stringWithString:(NSString *)string NS_RETURNS_RETAINED { - ASTextBackedString *one = [self new]; - one.string = string; - return one; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder { - [aCoder encodeObject:self.string forKey:@"string"]; -} - -- (id)initWithCoder:(NSCoder *)aDecoder { - self = [super init]; - _string = [aDecoder decodeObjectForKey:@"string"]; - return self; -} - -- (id)copyWithZone:(NSZone *)zone { - __typeof__(self) one = [self.class new]; - one.string = self.string; - return one; -} - -@end - - -@implementation ASTextBinding - -+ (instancetype)bindingWithDeleteConfirm:(BOOL)deleteConfirm NS_RETURNS_RETAINED { - ASTextBinding *one = [self new]; - one.deleteConfirm = deleteConfirm; - return one; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder { - [aCoder encodeObject:@(self.deleteConfirm) forKey:@"deleteConfirm"]; -} - -- (id)initWithCoder:(NSCoder *)aDecoder { - self = [super init]; - _deleteConfirm = ((NSNumber *)[aDecoder decodeObjectForKey:@"deleteConfirm"]).boolValue; - return self; -} - -- (id)copyWithZone:(NSZone *)zone { - __typeof__(self) one = [self.class new]; - one.deleteConfirm = self.deleteConfirm; - return one; -} - -@end - - -@implementation ASTextShadow - -+ (instancetype)shadowWithColor:(UIColor *)color offset:(CGSize)offset radius:(CGFloat)radius NS_RETURNS_RETAINED { - ASTextShadow *one = [self new]; - one.color = color; - one.offset = offset; - one.radius = radius; - return one; -} - -+ (instancetype)shadowWithNSShadow:(NSShadow *)nsShadow NS_RETURNS_RETAINED { - if (!nsShadow) return nil; - ASTextShadow *shadow = [self new]; - shadow.offset = nsShadow.shadowOffset; - shadow.radius = nsShadow.shadowBlurRadius; - id color = nsShadow.shadowColor; - if (color) { - if (CGColorGetTypeID() == CFGetTypeID((__bridge CFTypeRef)(color))) { - color = [UIColor colorWithCGColor:(__bridge CGColorRef)(color)]; - } - if ([color isKindOfClass:[UIColor class]]) { - shadow.color = color; - } - } - return shadow; -} - -- (NSShadow *)nsShadow { - NSShadow *shadow = [NSShadow new]; - shadow.shadowOffset = self.offset; - shadow.shadowBlurRadius = self.radius; - shadow.shadowColor = self.color; - return shadow; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder { - [aCoder encodeObject:self.color forKey:@"color"]; - [aCoder encodeObject:@(self.radius) forKey:@"radius"]; - [aCoder encodeObject:[NSValue valueWithCGSize:self.offset] forKey:@"offset"]; - [aCoder encodeObject:self.subShadow forKey:@"subShadow"]; -} - -- (id)initWithCoder:(NSCoder *)aDecoder { - self = [super init]; - _color = [aDecoder decodeObjectForKey:@"color"]; - _radius = ((NSNumber *)[aDecoder decodeObjectForKey:@"radius"]).floatValue; - _offset = ((NSValue *)[aDecoder decodeObjectForKey:@"offset"]).CGSizeValue; - _subShadow = [aDecoder decodeObjectForKey:@"subShadow"]; - return self; -} - -- (id)copyWithZone:(NSZone *)zone { - __typeof__(self) one = [self.class new]; - one.color = self.color; - one.radius = self.radius; - one.offset = self.offset; - one.subShadow = self.subShadow.copy; - return one; -} - -@end - - -@implementation ASTextDecoration - -- (instancetype)init { - self = [super init]; - _style = ASTextLineStyleSingle; - return self; -} - -+ (instancetype)decorationWithStyle:(ASTextLineStyle)style NS_RETURNS_RETAINED { - ASTextDecoration *one = [self new]; - one.style = style; - return one; -} -+ (instancetype)decorationWithStyle:(ASTextLineStyle)style width:(NSNumber *)width color:(UIColor *)color NS_RETURNS_RETAINED { - ASTextDecoration *one = [self new]; - one.style = style; - one.width = width; - one.color = color; - return one; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder { - [aCoder encodeObject:@(self.style) forKey:@"style"]; - [aCoder encodeObject:self.width forKey:@"width"]; - [aCoder encodeObject:self.color forKey:@"color"]; -} - -- (id)initWithCoder:(NSCoder *)aDecoder { - self = [super init]; - self.style = ((NSNumber *)[aDecoder decodeObjectForKey:@"style"]).unsignedIntegerValue; - self.width = [aDecoder decodeObjectForKey:@"width"]; - self.color = [aDecoder decodeObjectForKey:@"color"]; - return self; -} - -- (id)copyWithZone:(NSZone *)zone { - __typeof__(self) one = [self.class new]; - one.style = self.style; - one.width = self.width; - one.color = self.color; - return one; -} - -@end - - -@implementation ASTextBorder - -+ (instancetype)borderWithLineStyle:(ASTextLineStyle)lineStyle lineWidth:(CGFloat)width strokeColor:(UIColor *)color NS_RETURNS_RETAINED { - ASTextBorder *one = [self new]; - one.lineStyle = lineStyle; - one.strokeWidth = width; - one.strokeColor = color; - return one; -} - -+ (instancetype)borderWithFillColor:(UIColor *)color cornerRadius:(CGFloat)cornerRadius NS_RETURNS_RETAINED { - ASTextBorder *one = [self new]; - one.fillColor = color; - one.cornerRadius = cornerRadius; - one.insets = UIEdgeInsetsMake(-2, 0, 0, -2); - return one; -} - -- (instancetype)init { - self = [super init]; - self.lineStyle = ASTextLineStyleSingle; - return self; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder { - [aCoder encodeObject:@(self.lineStyle) forKey:@"lineStyle"]; - [aCoder encodeObject:@(self.strokeWidth) forKey:@"strokeWidth"]; - [aCoder encodeObject:self.strokeColor forKey:@"strokeColor"]; - [aCoder encodeObject:@(self.lineJoin) forKey:@"lineJoin"]; - [aCoder encodeObject:[NSValue valueWithUIEdgeInsets:self.insets] forKey:@"insets"]; - [aCoder encodeObject:@(self.cornerRadius) forKey:@"cornerRadius"]; - [aCoder encodeObject:self.shadow forKey:@"shadow"]; - [aCoder encodeObject:self.fillColor forKey:@"fillColor"]; -} - -- (id)initWithCoder:(NSCoder *)aDecoder { - self = [super init]; - _lineStyle = ((NSNumber *)[aDecoder decodeObjectForKey:@"lineStyle"]).unsignedIntegerValue; - _strokeWidth = ((NSNumber *)[aDecoder decodeObjectForKey:@"strokeWidth"]).doubleValue; - _strokeColor = [aDecoder decodeObjectForKey:@"strokeColor"]; - _lineJoin = (CGLineJoin)((NSNumber *)[aDecoder decodeObjectForKey:@"join"]).unsignedIntegerValue; - _insets = ((NSValue *)[aDecoder decodeObjectForKey:@"insets"]).UIEdgeInsetsValue; - _cornerRadius = ((NSNumber *)[aDecoder decodeObjectForKey:@"cornerRadius"]).doubleValue; - _shadow = [aDecoder decodeObjectForKey:@"shadow"]; - _fillColor = [aDecoder decodeObjectForKey:@"fillColor"]; - return self; -} - -- (id)copyWithZone:(NSZone *)zone { - __typeof__(self) one = [self.class new]; - one.lineStyle = self.lineStyle; - one.strokeWidth = self.strokeWidth; - one.strokeColor = self.strokeColor; - one.lineJoin = self.lineJoin; - one.insets = self.insets; - one.cornerRadius = self.cornerRadius; - one.shadow = self.shadow.copy; - one.fillColor = self.fillColor; - return one; -} - -@end - - -@implementation ASTextAttachment - -+ (instancetype)attachmentWithContent:(id)content NS_RETURNS_RETAINED { - ASTextAttachment *one = [self new]; - one.content = content; - return one; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder { - [aCoder encodeObject:self.content forKey:@"content"]; - [aCoder encodeObject:[NSValue valueWithUIEdgeInsets:self.contentInsets] forKey:@"contentInsets"]; - [aCoder encodeObject:self.userInfo forKey:@"userInfo"]; -} - -- (id)initWithCoder:(NSCoder *)aDecoder { - self = [super init]; - _content = [aDecoder decodeObjectForKey:@"content"]; - _contentInsets = ((NSValue *)[aDecoder decodeObjectForKey:@"contentInsets"]).UIEdgeInsetsValue; - _userInfo = [aDecoder decodeObjectForKey:@"userInfo"]; - return self; -} - -- (id)copyWithZone:(NSZone *)zone { - __typeof__(self) one = [self.class new]; - if ([self.content respondsToSelector:@selector(copy)]) { - one.content = [self.content copy]; - } else { - one.content = self.content; - } - one.contentInsets = self.contentInsets; - one.userInfo = self.userInfo.copy; - return one; -} - -@end - - -@implementation ASTextHighlight - -+ (instancetype)highlightWithAttributes:(NSDictionary *)attributes NS_RETURNS_RETAINED { - ASTextHighlight *one = [self new]; - one.attributes = attributes; - return one; -} - -+ (instancetype)highlightWithBackgroundColor:(UIColor *)color NS_RETURNS_RETAINED { - ASTextBorder *highlightBorder = [ASTextBorder new]; - highlightBorder.insets = UIEdgeInsetsMake(-2, -1, -2, -1); - highlightBorder.cornerRadius = 3; - highlightBorder.fillColor = color; - - ASTextHighlight *one = [self new]; - [one setBackgroundBorder:highlightBorder]; - return one; -} - -- (void)setAttributes:(NSDictionary *)attributes { - _attributes = attributes.mutableCopy; -} - -- (id)copyWithZone:(NSZone *)zone { - __typeof__(self) one = [self.class new]; - one.attributes = self.attributes.mutableCopy; - return one; -} - -- (void)_makeMutableAttributes { - if (!_attributes) { - _attributes = [NSMutableDictionary new]; - } else if (![_attributes isKindOfClass:[NSMutableDictionary class]]) { - _attributes = _attributes.mutableCopy; - } -} - -- (void)setFont:(UIFont *)font { - [self _makeMutableAttributes]; - if (font == (id)[NSNull null] || font == nil) { - ((NSMutableDictionary *)_attributes)[(id)kCTFontAttributeName] = [NSNull null]; - } else { - CTFontRef ctFont = CTFontCreateWithName((__bridge CFStringRef)font.fontName, font.pointSize, NULL); - if (ctFont) { - ((NSMutableDictionary *)_attributes)[(id)kCTFontAttributeName] = (__bridge id)(ctFont); - CFRelease(ctFont); - } - } -} - -- (void)setColor:(UIColor *)color { - [self _makeMutableAttributes]; - if (color == (id)[NSNull null] || color == nil) { - ((NSMutableDictionary *)_attributes)[(id)kCTForegroundColorAttributeName] = [NSNull null]; - ((NSMutableDictionary *)_attributes)[NSForegroundColorAttributeName] = [NSNull null]; - } else { - ((NSMutableDictionary *)_attributes)[(id)kCTForegroundColorAttributeName] = (__bridge id)(color.CGColor); - ((NSMutableDictionary *)_attributes)[NSForegroundColorAttributeName] = color; - } -} - -- (void)setStrokeWidth:(NSNumber *)width { - [self _makeMutableAttributes]; - if (width == (id)[NSNull null] || width == nil) { - ((NSMutableDictionary *)_attributes)[(id)kCTStrokeWidthAttributeName] = [NSNull null]; - } else { - ((NSMutableDictionary *)_attributes)[(id)kCTStrokeWidthAttributeName] = width; - } -} - -- (void)setStrokeColor:(UIColor *)color { - [self _makeMutableAttributes]; - if (color == (id)[NSNull null] || color == nil) { - ((NSMutableDictionary *)_attributes)[(id)kCTStrokeColorAttributeName] = [NSNull null]; - ((NSMutableDictionary *)_attributes)[NSStrokeColorAttributeName] = [NSNull null]; - } else { - ((NSMutableDictionary *)_attributes)[(id)kCTStrokeColorAttributeName] = (__bridge id)(color.CGColor); - ((NSMutableDictionary *)_attributes)[NSStrokeColorAttributeName] = color; - } -} - -- (void)setTextAttribute:(NSString *)attribute value:(id)value { - [self _makeMutableAttributes]; - if (value == nil) value = [NSNull null]; - ((NSMutableDictionary *)_attributes)[attribute] = value; -} - -- (void)setShadow:(ASTextShadow *)shadow { - [self setTextAttribute:ASTextShadowAttributeName value:shadow]; -} - -- (void)setInnerShadow:(ASTextShadow *)shadow { - [self setTextAttribute:ASTextInnerShadowAttributeName value:shadow]; -} - -- (void)setUnderline:(ASTextDecoration *)underline { - [self setTextAttribute:ASTextUnderlineAttributeName value:underline]; -} - -- (void)setStrikethrough:(ASTextDecoration *)strikethrough { - [self setTextAttribute:ASTextStrikethroughAttributeName value:strikethrough]; -} - -- (void)setBackgroundBorder:(ASTextBorder *)border { - [self setTextAttribute:ASTextBackgroundBorderAttributeName value:border]; -} - -- (void)setBorder:(ASTextBorder *)border { - [self setTextAttribute:ASTextBorderAttributeName value:border]; -} - -- (void)setAttachment:(ASTextAttachment *)attachment { - [self setTextAttribute:ASTextAttachmentAttributeName value:attachment]; -} - -@end - diff --git a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/String/ASTextRunDelegate.h b/submodules/AsyncDisplayKit/Source/Private/TextExperiment/String/ASTextRunDelegate.h deleted file mode 100644 index 3d3bf11c2c..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/String/ASTextRunDelegate.h +++ /dev/null @@ -1,65 +0,0 @@ -// -// ASTextRunDelegate.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - Wrapper for CTRunDelegateRef. - - Example: - - ASTextRunDelegate *delegate = [ASTextRunDelegate new]; - delegate.ascent = 20; - delegate.descent = 4; - delegate.width = 20; - CTRunDelegateRef ctRunDelegate = delegate.CTRunDelegate; - if (ctRunDelegate) { - /// add to attributed string - CFRelease(ctRunDelegate); - } - - */ -@interface ASTextRunDelegate : NSObject - -/** - Creates and returns the CTRunDelegate. - - @discussion You need call CFRelease() after used. - The CTRunDelegateRef has a strong reference to this ASTextRunDelegate object. - In CoreText, use CTRunDelegateGetRefCon() to get this ASTextRunDelegate object. - - @return The CTRunDelegate object. - */ -- (nullable CTRunDelegateRef)CTRunDelegate CF_RETURNS_RETAINED; - -/** - Additional information about the the run delegate. - */ -@property (nullable, nonatomic) NSDictionary *userInfo; - -/** - The typographic ascent of glyphs in the run. - */ -@property (nonatomic) CGFloat ascent; - -/** - The typographic descent of glyphs in the run. - */ -@property (nonatomic) CGFloat descent; - -/** - The typographic width of glyphs in the run. - */ -@property (nonatomic) CGFloat width; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/String/ASTextRunDelegate.mm b/submodules/AsyncDisplayKit/Source/Private/TextExperiment/String/ASTextRunDelegate.mm deleted file mode 100644 index 1c179b1fea..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/String/ASTextRunDelegate.mm +++ /dev/null @@ -1,68 +0,0 @@ -// -// ASTextRunDelegate.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -static void DeallocCallback(void *ref) { - ASTextRunDelegate *self = (__bridge_transfer ASTextRunDelegate *)(ref); - self = nil; // release -} - -static CGFloat GetAscentCallback(void *ref) { - ASTextRunDelegate *self = (__bridge ASTextRunDelegate *)(ref); - return self.ascent; -} - -static CGFloat GetDecentCallback(void *ref) { - ASTextRunDelegate *self = (__bridge ASTextRunDelegate *)(ref); - return self.descent; -} - -static CGFloat GetWidthCallback(void *ref) { - ASTextRunDelegate *self = (__bridge ASTextRunDelegate *)(ref); - return self.width; -} - -@implementation ASTextRunDelegate - -- (CTRunDelegateRef)CTRunDelegate CF_RETURNS_RETAINED { - CTRunDelegateCallbacks callbacks; - callbacks.version = kCTRunDelegateCurrentVersion; - callbacks.dealloc = DeallocCallback; - callbacks.getAscent = GetAscentCallback; - callbacks.getDescent = GetDecentCallback; - callbacks.getWidth = GetWidthCallback; - return CTRunDelegateCreate(&callbacks, (__bridge_retained void *)(self.copy)); -} - -- (void)encodeWithCoder:(NSCoder *)aCoder { - [aCoder encodeObject:@(_ascent) forKey:@"ascent"]; - [aCoder encodeObject:@(_descent) forKey:@"descent"]; - [aCoder encodeObject:@(_width) forKey:@"width"]; - [aCoder encodeObject:_userInfo forKey:@"userInfo"]; -} - -- (id)initWithCoder:(NSCoder *)aDecoder { - self = [super init]; - _ascent = ((NSNumber *)[aDecoder decodeObjectForKey:@"ascent"]).floatValue; - _descent = ((NSNumber *)[aDecoder decodeObjectForKey:@"descent"]).floatValue; - _width = ((NSNumber *)[aDecoder decodeObjectForKey:@"width"]).floatValue; - _userInfo = [aDecoder decodeObjectForKey:@"userInfo"]; - return self; -} - -- (id)copyWithZone:(NSZone *)zone { - __typeof__(self) one = [self.class new]; - one.ascent = self.ascent; - one.descent = self.descent; - one.width = self.width; - one.userInfo = self.userInfo; - return one; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Utility/ASTextUtilities.h b/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Utility/ASTextUtilities.h deleted file mode 100644 index f21a931ba8..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Utility/ASTextUtilities.h +++ /dev/null @@ -1,316 +0,0 @@ -// -// ASTextUtilities.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import -#import -#import - - -#ifndef ASTEXT_CLAMP // return the clamped value -#define ASTEXT_CLAMP(_x_, _low_, _high_) (((_x_) > (_high_)) ? (_high_) : (((_x_) < (_low_)) ? (_low_) : (_x_))) -#endif - -#ifndef ASTEXT_SWAP // swap two value -#define ASTEXT_SWAP(_a_, _b_) do { __typeof__(_a_) _tmp_ = (_a_); (_a_) = (_b_); (_b_) = _tmp_; } while (0) -#endif - -NS_ASSUME_NONNULL_BEGIN - -/** - Whether the character is 'line break char': - U+000D (\\r or CR) - U+2028 (Unicode line separator) - U+000A (\\n or LF) - U+2029 (Unicode paragraph separator) - - @param c A character - @return YES or NO. - */ -static inline BOOL ASTextIsLinebreakChar(unichar c) { - switch (c) { - case 0x000D: - case 0x2028: - case 0x000A: - case 0x2029: - return YES; - default: - return NO; - } -} - -/** - Whether the string is a 'line break': - U+000D (\\r or CR) - U+2028 (Unicode line separator) - U+000A (\\n or LF) - U+2029 (Unicode paragraph separator) - \\r\\n, in that order (also known as CRLF) - - @param str A string - @return YES or NO. - */ -static inline BOOL ASTextIsLinebreakString(NSString * _Nullable str) { - if (str.length > 2 || str.length == 0) return NO; - if (str.length == 1) { - unichar c = [str characterAtIndex:0]; - return ASTextIsLinebreakChar(c); - } else { - return ([str characterAtIndex:0] == '\r') && ([str characterAtIndex:1] == '\n'); - } -} - -/** - If the string has a 'line break' suffix, return the 'line break' length. - - @param str A string. - @return The length of the tail line break: 0, 1 or 2. - */ -static inline NSUInteger ASTextLinebreakTailLength(NSString * _Nullable str) { - if (str.length >= 2) { - unichar c2 = [str characterAtIndex:str.length - 1]; - if (ASTextIsLinebreakChar(c2)) { - unichar c1 = [str characterAtIndex:str.length - 2]; - if (c1 == '\r' && c2 == '\n') return 2; - else return 1; - } else { - return 0; - } - } else if (str.length == 1) { - return ASTextIsLinebreakChar([str characterAtIndex:0]) ? 1 : 0; - } else { - return 0; - } -} - -/** - Whether the font contains color bitmap glyphs. - - @discussion Only `AppleColorEmoji` contains color bitmap glyphs in iOS system fonts. - @param font A font. - @return YES: the font contains color bitmap glyphs, NO: the font has no color bitmap glyph. - */ -static inline BOOL ASTextCTFontContainsColorBitmapGlyphs(CTFontRef font) { - return (CTFontGetSymbolicTraits(font) & kCTFontTraitColorGlyphs) != 0; -} - -/** - Get the `AppleColorEmoji` font's ascent with a specified font size. - It may used to create custom emoji. - - @param fontSize The specified font size. - @return The font ascent. - */ -static inline CGFloat ASTextEmojiGetAscentWithFontSize(CGFloat fontSize) { - if (fontSize < 16) { - return 1.25 * fontSize; - } else if (16 <= fontSize && fontSize <= 24) { - return 0.5 * fontSize + 12; - } else { - return fontSize; - } -} - -/** - Get the `AppleColorEmoji` font's descent with a specified font size. - It may used to create custom emoji. - - @param fontSize The specified font size. - @return The font descent. - */ -static inline CGFloat ASTextEmojiGetDescentWithFontSize(CGFloat fontSize) { - if (fontSize < 16) { - return 0.390625 * fontSize; - } else if (16 <= fontSize && fontSize <= 24) { - return 0.15625 * fontSize + 3.75; - } else { - return 0.3125 * fontSize; - } - return 0; -} - -/** - Get the `AppleColorEmoji` font's glyph bounding rect with a specified font size. - It may used to create custom emoji. - - @param fontSize The specified font size. - @return The font glyph bounding rect. - */ -static inline CGRect ASTextEmojiGetGlyphBoundingRectWithFontSize(CGFloat fontSize) { - CGRect rect; - rect.origin.x = 0.75; - rect.size.width = rect.size.height = ASTextEmojiGetAscentWithFontSize(fontSize); - if (fontSize < 16) { - rect.origin.y = -0.2525 * fontSize; - } else if (16 <= fontSize && fontSize <= 24) { - rect.origin.y = 0.1225 * fontSize -6; - } else { - rect.origin.y = -0.1275 * fontSize; - } - return rect; -} - - -/** - Get the character set which should rotate in vertical form. - @return The shared character set. - */ -NSCharacterSet *ASTextVerticalFormRotateCharacterSet(void); - -/** - Get the character set which should rotate and move in vertical form. - @return The shared character set. - */ -NSCharacterSet *ASTextVerticalFormRotateAndMoveCharacterSet(void); - - -/// Get the transform rotation. -/// @return the rotation in radians [-PI,PI] ([-180°,180°]) -static inline CGFloat ASTextCGAffineTransformGetRotation(CGAffineTransform transform) { - return atan2(transform.b, transform.a); -} - -/// Negates/inverts a UIEdgeInsets. -static inline UIEdgeInsets ASTextUIEdgeInsetsInvert(UIEdgeInsets insets) { - return UIEdgeInsetsMake(-insets.top, -insets.left, -insets.bottom, -insets.right); -} - -/** - Returns a rectangle to fit `rect` with specified content mode. - - @param rect The constraint rect - @param size The content size - @param mode The content mode - @return A rectangle for the given content mode. - @discussion UIViewContentModeRedraw is same as UIViewContentModeScaleToFill. - */ -CGRect ASTextCGRectFitWithContentMode(CGRect rect, CGSize size, UIViewContentMode mode); - -/// Returns the center for the rectangle. -static inline CGPoint ASTextCGRectGetCenter(CGRect rect) { - return CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect)); -} - -/// Returns the area of the rectangle. -static inline CGFloat ASTextCGRectGetArea(CGRect rect) { - if (CGRectIsNull(rect)) return 0; - rect = CGRectStandardize(rect); - return rect.size.width * rect.size.height; -} - -/// Returns the minmium distance between a point to a rectangle. -static inline CGFloat ASTextCGPointGetDistanceToRect(CGPoint p, CGRect r) { - r = CGRectStandardize(r); - if (CGRectContainsPoint(r, p)) return 0; - CGFloat distV, distH; - if (CGRectGetMinY(r) <= p.y && p.y <= CGRectGetMaxY(r)) { - distV = 0; - } else { - distV = p.y < CGRectGetMinY(r) ? CGRectGetMinY(r) - p.y : p.y - CGRectGetMaxY(r); - } - if (CGRectGetMinX(r) <= p.x && p.x <= CGRectGetMaxX(r)) { - distH = 0; - } else { - distH = p.x < CGRectGetMinX(r) ? CGRectGetMinX(r) - p.x : p.x - CGRectGetMaxX(r); - } - return MAX(distV, distH); -} - -/// Convert point to pixel. -static inline CGFloat ASTextCGFloatToPixel(CGFloat value) { - return value * ASScreenScale(); -} - -/// Convert pixel to point. -static inline CGFloat ASTextCGFloatFromPixel(CGFloat value) { - return value / ASScreenScale(); -} - -/// round point value to .5 pixel for path stroke (odd pixel line width pixel-aligned) -static inline CGFloat ASTextCGFloatPixelHalf(CGFloat value) { - CGFloat scale = ASScreenScale(); - return (floor(value * scale) + 0.5) / scale; -} - -/// floor point value for pixel-aligned -static inline CGPoint ASTextCGPointPixelFloor(CGPoint point) { - CGFloat scale = ASScreenScale(); - return CGPointMake(floor(point.x * scale) / scale, - floor(point.y * scale) / scale); -} - -/// round point value for pixel-aligned -static inline CGPoint ASTextCGPointPixelRound(CGPoint point) { - CGFloat scale = ASScreenScale(); - return CGPointMake(round(point.x * scale) / scale, - round(point.y * scale) / scale); -} - -/// ceil point value for pixel-aligned -static inline CGPoint ASTextCGPointPixelCeil(CGPoint point) { - CGFloat scale = ASScreenScale(); - return CGPointMake(ceil(point.x * scale) / scale, - ceil(point.y * scale) / scale); -} - -/// round point value to .5 pixel for path stroke (odd pixel line width pixel-aligned) -static inline CGPoint ASTextCGPointPixelHalf(CGPoint point) { - CGFloat scale = ASScreenScale(); - return CGPointMake((floor(point.x * scale) + 0.5) / scale, - (floor(point.y * scale) + 0.5) / scale); -} - -/// round point value for pixel-aligned -static inline CGRect ASTextCGRectPixelRound(CGRect rect) { - CGPoint origin = ASTextCGPointPixelRound(rect.origin); - CGPoint corner = ASTextCGPointPixelRound(CGPointMake(rect.origin.x + rect.size.width, - rect.origin.y + rect.size.height)); - return CGRectMake(origin.x, origin.y, corner.x - origin.x, corner.y - origin.y); -} - -/// round point value to .5 pixel for path stroke (odd pixel line width pixel-aligned) -static inline CGRect ASTextCGRectPixelHalf(CGRect rect) { - CGPoint origin = ASTextCGPointPixelHalf(rect.origin); - CGPoint corner = ASTextCGPointPixelHalf(CGPointMake(rect.origin.x + rect.size.width, - rect.origin.y + rect.size.height)); - return CGRectMake(origin.x, origin.y, corner.x - origin.x, corner.y - origin.y); -} - - -static inline UIFont * _Nullable ASTextFontWithBold(UIFont *font) { - return [UIFont fontWithDescriptor:[font.fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold] size:font.pointSize]; -} - -static inline UIFont * _Nullable ASTextFontWithItalic(UIFont *font) { - return [UIFont fontWithDescriptor:[font.fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitItalic] size:font.pointSize]; -} - -static inline UIFont * _Nullable ASTextFontWithBoldItalic(UIFont *font) { - return [UIFont fontWithDescriptor:[font.fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold | UIFontDescriptorTraitItalic] size:font.pointSize]; -} - - - -/** - Convert CFRange to NSRange - @param range CFRange @return NSRange - */ -static inline NSRange ASTextNSRangeFromCFRange(CFRange range) { - return NSMakeRange(range.location, range.length); -} - -/** - Convert NSRange to CFRange - @param range NSRange @return CFRange - */ -static inline CFRange ASTextCFRangeFromNSRange(NSRange range) { - return CFRangeMake(range.location, range.length); -} - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Utility/ASTextUtilities.mm b/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Utility/ASTextUtilities.mm deleted file mode 100644 index 8d0137718b..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Utility/ASTextUtilities.mm +++ /dev/null @@ -1,143 +0,0 @@ -// -// ASTextUtilities.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASTextUtilities.h" -#import - -NSCharacterSet *ASTextVerticalFormRotateCharacterSet() { - static NSMutableCharacterSet *set; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - set = [NSMutableCharacterSet new]; - [set addCharactersInRange:NSMakeRange(0x1100, 256)]; // Hangul Jamo - [set addCharactersInRange:NSMakeRange(0x2460, 160)]; // Enclosed Alphanumerics - [set addCharactersInRange:NSMakeRange(0x2600, 256)]; // Miscellaneous Symbols - [set addCharactersInRange:NSMakeRange(0x2700, 192)]; // Dingbats - [set addCharactersInRange:NSMakeRange(0x2E80, 128)]; // CJK Radicals Supplement - [set addCharactersInRange:NSMakeRange(0x2F00, 224)]; // Kangxi Radicals - [set addCharactersInRange:NSMakeRange(0x2FF0, 16)]; // Ideographic Description Characters - [set addCharactersInRange:NSMakeRange(0x3000, 64)]; // CJK Symbols and Punctuation - [set removeCharactersInRange:NSMakeRange(0x3008, 10)]; - [set removeCharactersInRange:NSMakeRange(0x3014, 12)]; - [set addCharactersInRange:NSMakeRange(0x3040, 96)]; // Hiragana - [set addCharactersInRange:NSMakeRange(0x30A0, 96)]; // Katakana - [set addCharactersInRange:NSMakeRange(0x3100, 48)]; // Bopomofo - [set addCharactersInRange:NSMakeRange(0x3130, 96)]; // Hangul Compatibility Jamo - [set addCharactersInRange:NSMakeRange(0x3190, 16)]; // Kanbun - [set addCharactersInRange:NSMakeRange(0x31A0, 32)]; // Bopomofo Extended - [set addCharactersInRange:NSMakeRange(0x31C0, 48)]; // CJK Strokes - [set addCharactersInRange:NSMakeRange(0x31F0, 16)]; // Katakana Phonetic Extensions - [set addCharactersInRange:NSMakeRange(0x3200, 256)]; // Enclosed CJK Letters and Months - [set addCharactersInRange:NSMakeRange(0x3300, 256)]; // CJK Compatibility - [set addCharactersInRange:NSMakeRange(0x3400, 2582)]; // CJK Unified Ideographs Extension A - [set addCharactersInRange:NSMakeRange(0x4E00, 20941)]; // CJK Unified Ideographs - [set addCharactersInRange:NSMakeRange(0xAC00, 11172)]; // Hangul Syllables - [set addCharactersInRange:NSMakeRange(0xD7B0, 80)]; // Hangul Jamo Extended-B - [set addCharactersInString:@""]; // U+F8FF (Private Use Area) - [set addCharactersInRange:NSMakeRange(0xF900, 512)]; // CJK Compatibility Ideographs - [set addCharactersInRange:NSMakeRange(0xFE10, 16)]; // Vertical Forms - [set addCharactersInRange:NSMakeRange(0xFF00, 240)]; // Halfwidth and Fullwidth Forms - [set addCharactersInRange:NSMakeRange(0x1F200, 256)]; // Enclosed Ideographic Supplement - [set addCharactersInRange:NSMakeRange(0x1F300, 768)]; // Enclosed Ideographic Supplement - [set addCharactersInRange:NSMakeRange(0x1F600, 80)]; // Emoticons (Emoji) - [set addCharactersInRange:NSMakeRange(0x1F680, 128)]; // Transport and Map Symbols - - // See http://unicode-table.com/ for more information. - }); - return set; -} - -NSCharacterSet *ASTextVerticalFormRotateAndMoveCharacterSet() { - static NSMutableCharacterSet *set; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - set = [NSMutableCharacterSet new]; - [set addCharactersInString:@",。、."]; - }); - return set; -} - -CGRect ASTextCGRectFitWithContentMode(CGRect rect, CGSize size, UIViewContentMode mode) { - rect = CGRectStandardize(rect); - size.width = size.width < 0 ? -size.width : size.width; - size.height = size.height < 0 ? -size.height : size.height; - CGPoint center = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect)); - switch (mode) { - case UIViewContentModeScaleAspectFit: - case UIViewContentModeScaleAspectFill: { - if (rect.size.width < 0.01 || rect.size.height < 0.01 || - size.width < 0.01 || size.height < 0.01) { - rect.origin = center; - rect.size = CGSizeZero; - } else { - CGFloat scale; - if (mode == UIViewContentModeScaleAspectFit) { - if (size.width / size.height < rect.size.width / rect.size.height) { - scale = rect.size.height / size.height; - } else { - scale = rect.size.width / size.width; - } - } else { - if (size.width / size.height < rect.size.width / rect.size.height) { - scale = rect.size.width / size.width; - } else { - scale = rect.size.height / size.height; - } - } - size.width *= scale; - size.height *= scale; - rect.size = size; - rect.origin = CGPointMake(center.x - size.width * 0.5, center.y - size.height * 0.5); - } - } break; - case UIViewContentModeCenter: { - rect.size = size; - rect.origin = CGPointMake(center.x - size.width * 0.5, center.y - size.height * 0.5); - } break; - case UIViewContentModeTop: { - rect.origin.x = center.x - size.width * 0.5; - rect.size = size; - } break; - case UIViewContentModeBottom: { - rect.origin.x = center.x - size.width * 0.5; - rect.origin.y += rect.size.height - size.height; - rect.size = size; - } break; - case UIViewContentModeLeft: { - rect.origin.y = center.y - size.height * 0.5; - rect.size = size; - } break; - case UIViewContentModeRight: { - rect.origin.y = center.y - size.height * 0.5; - rect.origin.x += rect.size.width - size.width; - rect.size = size; - } break; - case UIViewContentModeTopLeft: { - rect.size = size; - } break; - case UIViewContentModeTopRight: { - rect.origin.x += rect.size.width - size.width; - rect.size = size; - } break; - case UIViewContentModeBottomLeft: { - rect.origin.y += rect.size.height - size.height; - rect.size = size; - } break; - case UIViewContentModeBottomRight: { - rect.origin.x += rect.size.width - size.width; - rect.origin.y += rect.size.height - size.height; - rect.size = size; - } break; - case UIViewContentModeScaleToFill: - case UIViewContentModeRedraw: - default: { - rect = rect; - } - } - return rect; -} diff --git a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.h b/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.h deleted file mode 100644 index ef44fb4f35..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.h +++ /dev/null @@ -1,1375 +0,0 @@ -// -// NSAttributedString+ASText.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - Get pre-defined attributes from attributed string. - All properties defined in UIKit, CoreText and ASText are included. - */ -@interface NSAttributedString (ASText) - -#pragma mark - Retrieving character attribute information -///============================================================================= -/// @name Retrieving character attribute information -///============================================================================= - -/** - Returns the attributes at first charactor. - */ -@property (nullable, nonatomic, copy, readonly) NSDictionary *as_attributes; - -/** - Returns the attributes for the character at a given index. - - @discussion Raises an `NSRangeException` if index lies beyond the end of the - receiver's characters. - - @param index The index for which to return attributes. - This value must lie within the bounds of the receiver. - - @return The attributes for the character at index. - */ -- (nullable NSDictionary *)as_attributesAtIndex:(NSUInteger)index; - -/** - Returns the value for an attribute with a given name of the character at a given index. - - @discussion Raises an `NSRangeException` if index lies beyond the end of the - receiver's characters. - - @param attributeName The name of an attribute. - @param index The index for which to return attributes. - This value must not exceed the bounds of the receiver. - - @return The value for the attribute named `attributeName` of the character at - index `index`, or nil if there is no such attribute. - */ -- (nullable id)as_attribute:(NSString *)attributeName atIndex:(NSUInteger)index; - - -#pragma mark - Get character attribute as property -///============================================================================= -/// @name Get character attribute as property -///============================================================================= - -/** - The font of the text. (read-only) - - @discussion Default is Helvetica (Neue) 12. - @discussion Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:6.0 ASText:6.0 - */ -@property (nullable, nonatomic, readonly) UIFont *as_font; -- (nullable UIFont *)as_fontAtIndex:(NSUInteger)index; - -/** - A kerning adjustment. (read-only) - - @discussion Default is standard kerning. The kerning attribute indicate how many - points the following character should be shifted from its default offset as - defined by the current character's font in points; a positive kern indicates a - shift farther along and a negative kern indicates a shift closer to the current - character. If this attribute is not present, standard kerning will be used. - If this attribute is set to 0.0, no kerning will be done at all. - @discussion Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:6.0 ASText:6.0 - */ -@property (nullable, nonatomic, readonly) NSNumber *as_kern; -- (nullable NSNumber *)as_kernAtIndex:(NSUInteger)index; - -/** - The foreground color. (read-only) - - @discussion Default is Black. - @discussion Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:6.0 ASText:6.0 - */ -@property (nullable, nonatomic, readonly) UIColor *as_color; -- (nullable UIColor *)as_colorAtIndex:(NSUInteger)index; - -/** - The background color. (read-only) - - @discussion Default is nil (or no background). - @discussion Get this property returns the first character's attribute. - @since UIKit:6.0 - */ -@property (nullable, nonatomic, readonly) UIColor *as_backgroundColor; -- (nullable UIColor *)as_backgroundColorAtIndex:(NSUInteger)index; - -/** - The stroke width. (read-only) - - @discussion Default value is 0.0 (no stroke). This attribute, interpreted as - a percentage of font point size, controls the text drawing mode: positive - values effect drawing with stroke only; negative values are for stroke and fill. - A typical value for outlined text is 3.0. - @discussion Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:6.0 - */ -@property (nullable, nonatomic, readonly) NSNumber *as_strokeWidth; -- (nullable NSNumber *)as_strokeWidthAtIndex:(NSUInteger)index; - -/** - The stroke color. (read-only) - - @discussion Default value is nil (same as foreground color). - @discussion Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:6.0 - */ -@property (nullable, nonatomic, readonly) UIColor *as_strokeColor; -- (nullable UIColor *)as_strokeColorAtIndex:(NSUInteger)index; - -/** - The text shadow. (read-only) - - @discussion Default value is nil (no shadow). - @discussion Get this property returns the first character's attribute. - @since UIKit:6.0 ASText:6.0 - */ -@property (nullable, nonatomic, readonly) NSShadow *as_shadow; -- (nullable NSShadow *)as_shadowAtIndex:(NSUInteger)index; - -/** - The strikethrough style. (read-only) - - @discussion Default value is NSUnderlineStyleNone (no strikethrough). - @discussion Get this property returns the first character's attribute. - @since UIKit:6.0 - */ -@property (nonatomic, readonly) NSUnderlineStyle as_strikethroughStyle; -- (NSUnderlineStyle)as_strikethroughStyleAtIndex:(NSUInteger)index; - -/** - The strikethrough color. (read-only) - - @discussion Default value is nil (same as foreground color). - @discussion Get this property returns the first character's attribute. - @since UIKit:7.0 - */ -@property (nullable, nonatomic, readonly) UIColor *as_strikethroughColor; -- (nullable UIColor *)as_strikethroughColorAtIndex:(NSUInteger)index; - -/** - The underline style. (read-only) - - @discussion Default value is NSUnderlineStyleNone (no underline). - @discussion Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:6.0 - */ -@property (nonatomic, readonly) NSUnderlineStyle as_underlineStyle; -- (NSUnderlineStyle)as_underlineStyleAtIndex:(NSUInteger)index; - -/** - The underline color. (read-only) - - @discussion Default value is nil (same as foreground color). - @discussion Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:7.0 - */ -@property (nullable, nonatomic, readonly) UIColor *as_underlineColor; -- (nullable UIColor *)as_underlineColorAtIndex:(NSUInteger)index; - -/** - Ligature formation control. (read-only) - - @discussion Default is int value 1. The ligature attribute determines what kinds - of ligatures should be used when displaying the string. A value of 0 indicates - that only ligatures essential for proper rendering of text should be used, - 1 indicates that standard ligatures should be used, and 2 indicates that all - available ligatures should be used. Which ligatures are standard depends on the - script and possibly the font. - @discussion Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:6.0 ASText:6.0 - */ -@property (nullable, nonatomic, readonly) NSNumber *as_ligature; -- (nullable NSNumber *)as_ligatureAtIndex:(NSUInteger)index; - -/** - The text effect. (read-only) - - @discussion Default is nil (no effect). The only currently supported value - is NSTextEffectLetterpressStyle. - @discussion Get this property returns the first character's attribute. - @since UIKit:7.0 - */ -@property (nullable, nonatomic, readonly) NSString *as_textEffect; -- (nullable NSString *)as_textEffectAtIndex:(NSUInteger)index; - -/** - The skew to be applied to glyphs. (read-only) - - @discussion Default is 0 (no skew). - @discussion Get this property returns the first character's attribute. - @since UIKit:7.0 - */ -@property (nullable, nonatomic, readonly) NSNumber *as_obliqueness; -- (nullable NSNumber *)as_obliquenessAtIndex:(NSUInteger)index; - -/** - The log of the expansion factor to be applied to glyphs. (read-only) - - @discussion Default is 0 (no expansion). - @discussion Get this property returns the first character's attribute. - @since UIKit:7.0 - */ -@property (nullable, nonatomic, readonly) NSNumber *as_expansion; -- (nullable NSNumber *)as_expansionAtIndex:(NSUInteger)index; - -/** - The character's offset from the baseline, in points. (read-only) - - @discussion Default is 0. - @discussion Get this property returns the first character's attribute. - @since UIKit:7.0 - */ -@property (nullable, nonatomic, readonly) NSNumber *as_baselineOffset; -- (nullable NSNumber *)as_baselineOffsetAtIndex:(NSUInteger)index; - -/** - Glyph orientation control. (read-only) - - @discussion Default is NO. A value of NO indicates that horizontal glyph forms - are to be used, YES indicates that vertical glyph forms are to be used. - @discussion Get this property returns the first character's attribute. - @since CoreText:4.3 ASText:6.0 - */ -@property (nonatomic, readonly) BOOL as_verticalGlyphForm; -- (BOOL)as_verticalGlyphFormAtIndex:(NSUInteger)index; - -/** - Specifies text language. (read-only) - - @discussion Value must be a NSString containing a locale identifier. Default is - unset. When this attribute is set to a valid identifier, it will be used to select - localized glyphs (if supported by the font) and locale-specific line breaking rules. - @discussion Get this property returns the first character's attribute. - @since CoreText:7.0 ASText:7.0 - */ -@property (nullable, nonatomic, readonly) NSString *as_language; -- (nullable NSString *)as_languageAtIndex:(NSUInteger)index; - -/** - Specifies a bidirectional override or embedding. (read-only) - - @discussion See alse NSWritingDirection and NSWritingDirectionAttributeName. - @discussion Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:7.0 ASText:6.0 - */ -@property (nullable, nonatomic, readonly) NSArray *as_writingDirection; -- (nullable NSArray *)as_writingDirectionAtIndex:(NSUInteger)index; - -/** - An NSParagraphStyle object which is used to specify things like - line alignment, tab rulers, writing direction, etc. (read-only) - - @discussion Default is nil ([NSParagraphStyle defaultParagraphStyle]). - @discussion Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nullable, nonatomic, readonly) NSParagraphStyle *as_paragraphStyle; -- (nullable NSParagraphStyle *)as_paragraphStyleAtIndex:(NSUInteger)index; - -#pragma mark - Get paragraph attribute as property -///============================================================================= -/// @name Get paragraph attribute as property -///============================================================================= - -/** - The text alignment (A wrapper for NSParagraphStyle). (read-only) - - @discussion Natural text alignment is realized as left or right alignment - depending on the line sweep direction of the first script contained in the paragraph. - @discussion Default is NSTextAlignmentNatural. - @discussion Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic, readonly) NSTextAlignment as_alignment; -- (NSTextAlignment)as_alignmentAtIndex:(NSUInteger)index; - -/** - The mode that should be used to break lines (A wrapper for NSParagraphStyle). (read-only) - - @discussion This property contains the line break mode to be used laying out the paragraph's text. - @discussion Default is NSLineBreakByWordWrapping. - @discussion Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic, readonly) NSLineBreakMode as_lineBreakMode; -- (NSLineBreakMode)as_lineBreakModeAtIndex:(NSUInteger)index; - -/** - The distance in points between the bottom of one line fragment and the top of the next. - (A wrapper for NSParagraphStyle) (read-only) - - @discussion This value is always nonnegative. This value is included in the line - fragment heights in the layout manager. - @discussion Default is 0. - @discussion Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic, readonly) CGFloat as_lineSpacing; -- (CGFloat)as_lineSpacingAtIndex:(NSUInteger)index; - -/** - The space after the end of the paragraph (A wrapper for NSParagraphStyle). (read-only) - - @discussion This property contains the space (measured in points) added at the - end of the paragraph to separate it from the following paragraph. This value must - be nonnegative. The space between paragraphs is determined by adding the previous - paragraph's paragraphSpacing and the current paragraph's paragraphSpacingBefore. - @discussion Default is 0. - @discussion Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic, readonly) CGFloat as_paragraphSpacing; -- (CGFloat)as_paragraphSpacingAtIndex:(NSUInteger)index; - -/** - The distance between the paragraph's top and the beginning of its text content. - (A wrapper for NSParagraphStyle). (read-only) - - @discussion This property contains the space (measured in points) between the - paragraph's top and the beginning of its text content. - @discussion Default is 0. - @discussion Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic, readonly) CGFloat as_paragraphSpacingBefore; -- (CGFloat)as_paragraphSpacingBeforeAtIndex:(NSUInteger)index; - -/** - The indentation of the first line (A wrapper for NSParagraphStyle). (read-only) - - @discussion This property contains the distance (in points) from the leading margin - of a text container to the beginning of the paragraph's first line. This value - is always nonnegative. - @discussion Default is 0. - @discussion Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic, readonly) CGFloat as_firstLineHeadIndent; -- (CGFloat)as_firstLineHeadIndentAtIndex:(NSUInteger)index; - -/** - The indentation of the receiver's lines other than the first. (A wrapper for NSParagraphStyle). (read-only) - - @discussion This property contains the distance (in points) from the leading margin - of a text container to the beginning of lines other than the first. This value is - always nonnegative. - @discussion Default is 0. - @discussion Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic, readonly) CGFloat as_headIndent; -- (CGFloat)as_headIndentAtIndex:(NSUInteger)index; - -/** - The trailing indentation (A wrapper for NSParagraphStyle). (read-only) - - @discussion If positive, this value is the distance from the leading margin - (for example, the left margin in left-to-right text). If 0 or negative, it's the - distance from the trailing margin. - @discussion Default is 0. - @discussion Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic, readonly) CGFloat as_tailIndent; -- (CGFloat)as_tailIndentAtIndex:(NSUInteger)index; - -/** - The receiver's minimum height (A wrapper for NSParagraphStyle). (read-only) - - @discussion This property contains the minimum height in points that any line in - the receiver will occupy, regardless of the font size or size of any attached graphic. - This value must be nonnegative. - @discussion Default is 0. - @discussion Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic, readonly) CGFloat as_minimumLineHeight; -- (CGFloat)as_minimumLineHeightAtIndex:(NSUInteger)index; - -/** - The receiver's maximum line height (A wrapper for NSParagraphStyle). (read-only) - - @discussion This property contains the maximum height in points that any line in - the receiver will occupy, regardless of the font size or size of any attached graphic. - This value is always nonnegative. Glyphs and graphics exceeding this height will - overlap neighboring lines; however, a maximum height of 0 implies no line height limit. - Although this limit applies to the line itself, line spacing adds extra space between adjacent lines. - @discussion Default is 0 (no limit). - @discussion Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic, readonly) CGFloat as_maximumLineHeight; -- (CGFloat)as_maximumLineHeightAtIndex:(NSUInteger)index; - -/** - The line height multiple (A wrapper for NSParagraphStyle). (read-only) - - @discussion This property contains the line break mode to be used laying out the paragraph's text. - @discussion Default is 0 (no multiple). - @discussion Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic, readonly) CGFloat as_lineHeightMultiple; -- (CGFloat)as_lineHeightMultipleAtIndex:(NSUInteger)index; - -/** - The base writing direction (A wrapper for NSParagraphStyle). (read-only) - - @discussion If you specify NSWritingDirectionNaturalDirection, the receiver resolves - the writing direction to either NSWritingDirectionLeftToRight or NSWritingDirectionRightToLeft, - depending on the direction for the user's `language` preference setting. - @discussion Default is NSWritingDirectionNatural. - @discussion Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic, readonly) NSWritingDirection as_baseWritingDirection; -- (NSWritingDirection)as_baseWritingDirectionAtIndex:(NSUInteger)index; - -/** - The paragraph's threshold for hyphenation. (A wrapper for NSParagraphStyle). (read-only) - - @discussion Valid values lie between 0.0 and 1.0 inclusive. Hyphenation is attempted - when the ratio of the text width (as broken without hyphenation) to the width of the - line fragment is less than the hyphenation factor. When the paragraph's hyphenation - factor is 0.0, the layout manager's hyphenation factor is used instead. When both - are 0.0, hyphenation is disabled. - @discussion Default is 0. - @discussion Get this property returns the first character's attribute. - @since UIKit:6.0 - */ -@property (nonatomic, readonly) float as_hyphenationFactor; -- (float)as_hyphenationFactorAtIndex:(NSUInteger)index; - -/** - The document-wide default tab interval (A wrapper for NSParagraphStyle). (read-only) - - @discussion This property represents the default tab interval in points. Tabs after the - last specified in tabStops are placed at integer multiples of this distance (if positive). - @discussion Default is 0. - @discussion Get this property returns the first character's attribute. - @since CoreText:7.0 UIKit:7.0 ASText:7.0 - */ -@property (nonatomic, readonly) CGFloat as_defaultTabInterval; -- (CGFloat)as_defaultTabIntervalAtIndex:(NSUInteger)index; - -/** - An array of NSTextTab objects representing the receiver's tab stops. - (A wrapper for NSParagraphStyle). (read-only) - - @discussion The NSTextTab objects, sorted by location, define the tab stops for - the paragraph style. - @discussion Default is 12 TabStops with 28.0 tab interval. - @discussion Get this property returns the first character's attribute. - @since CoreText:7.0 UIKit:7.0 ASText:7.0 - */ -@property (nullable, nonatomic, copy, readonly) NSArray *as_tabStops; -- (nullable NSArray *)as_tabStopsAtIndex:(NSUInteger)index; - -#pragma mark - Get ASText attribute as property -///============================================================================= -/// @name Get ASText attribute as property -///============================================================================= - -/** - The text shadow. (read-only) - - @discussion Default value is nil (no shadow). - @discussion Get this property returns the first character's attribute. - @since ASText:6.0 - */ -@property (nullable, nonatomic, readonly) ASTextShadow *as_textShadow; -- (nullable ASTextShadow *)as_textShadowAtIndex:(NSUInteger)index; - -/** - The text inner shadow. (read-only) - - @discussion Default value is nil (no shadow). - @discussion Get this property returns the first character's attribute. - @since ASText:6.0 - */ -@property (nullable, nonatomic, readonly) ASTextShadow *as_textInnerShadow; -- (nullable ASTextShadow *)as_textInnerShadowAtIndex:(NSUInteger)index; - -/** - The text underline. (read-only) - - @discussion Default value is nil (no underline). - @discussion Get this property returns the first character's attribute. - @since ASText:6.0 - */ -@property (nullable, nonatomic, readonly) ASTextDecoration *as_textUnderline; -- (nullable ASTextDecoration *)as_textUnderlineAtIndex:(NSUInteger)index; - -/** - The text strikethrough. (read-only) - - @discussion Default value is nil (no strikethrough). - @discussion Get this property returns the first character's attribute. - @since ASText:6.0 - */ -@property (nullable, nonatomic, readonly) ASTextDecoration *as_textStrikethrough; -- (nullable ASTextDecoration *)as_textStrikethroughAtIndex:(NSUInteger)index; - -/** - The text border. (read-only) - - @discussion Default value is nil (no border). - @discussion Get this property returns the first character's attribute. - @since ASText:6.0 - */ -@property (nullable, nonatomic, readonly) ASTextBorder *as_textBorder; -- (nullable ASTextBorder *)as_textBorderAtIndex:(NSUInteger)index; - -/** - The text background border. (read-only) - - @discussion Default value is nil (no background border). - @discussion Get this property returns the first character's attribute. - @since ASText:6.0 - */ -@property (nullable, nonatomic, readonly) ASTextBorder *as_textBackgroundBorder; -- (nullable ASTextBorder *)as_textBackgroundBorderAtIndex:(NSUInteger)index; - -/** - The glyph transform. (read-only) - - @discussion Default value is CGAffineTransformIdentity (no transform). - @discussion Get this property returns the first character's attribute. - @since ASText:6.0 - */ -@property (nonatomic, readonly) CGAffineTransform as_textGlyphTransform; -- (CGAffineTransform)as_textGlyphTransformAtIndex:(NSUInteger)index; - - -#pragma mark - Query for ASText -///============================================================================= -/// @name Query for ASText -///============================================================================= - -/** - Returns the plain text from a range. - If there's `ASTextBackedStringAttributeName` attribute, the backed string will - replace the attributed string range. - - @param range A range in receiver. - @return The plain text. - */ -- (nullable NSString *)as_plainTextForRange:(NSRange)range; - - -#pragma mark - Create attachment string for ASText -///============================================================================= -/// @name Create attachment string for ASText -///============================================================================= - -/** - Creates and returns an attachment. - - @param content The attachment (UIImage/UIView/CALayer). - @param contentMode The attachment's content mode. - @param width The attachment's container width in layout. - @param ascent The attachment's container ascent in layout. - @param descent The attachment's container descent in layout. - - @return An attributed string, or nil if an error occurs. - @since ASText:6.0 - */ -+ (NSMutableAttributedString *)as_attachmentStringWithContent:(nullable id)content - contentMode:(UIViewContentMode)contentMode - width:(CGFloat)width - ascent:(CGFloat)ascent - descent:(CGFloat)descent; - -/** - Creates and returns an attachment. - - - Example: ContentMode:bottom Alignment:Top. - - The text The attachment holder - ↓ ↓ - ─────────┌──────────────────────┐─────── - / \ │ │ / ___| - / _ \ │ │| | - / ___ \ │ │| |___ ←── The text line - /_/ \_\│ ██████████████ │ \____| - ─────────│ ██████████████ │─────── - │ ██████████████ │ - │ ██████████████ ←───────────────── The attachment content - │ ██████████████ │ - └──────────────────────┘ - - @param content The attachment (UIImage/UIView/CALayer). - @param contentMode The attachment's content mode in attachment holder - @param attachmentSize The attachment holder's size in text layout. - @param font The attachment will align to this font. - @param alignment The attachment holder's alignment to text line. - - @return An attributed string, or nil if an error occurs. - @since ASText:6.0 - */ -+ (NSMutableAttributedString *)as_attachmentStringWithContent:(nullable id)content - contentMode:(UIViewContentMode)contentMode - attachmentSize:(CGSize)attachmentSize - alignToFont:(UIFont *)font - alignment:(ASTextVerticalAlignment)alignment; - -/** - Creates and returns an attahment from a fourquare image as if it was an emoji. - - @param image A fourquare image. - @param fontSize The font size. - - @return An attributed string, or nil if an error occurs. - @since ASText:6.0 - */ -+ (nullable NSMutableAttributedString *)as_attachmentStringWithEmojiImage:(UIImage *)image - fontSize:(CGFloat)fontSize; - -#pragma mark - Utility -///============================================================================= -/// @name Utility -///============================================================================= - -/** - Returns NSMakeRange(0, self.length). - */ -- (NSRange)as_rangeOfAll; - -/** - If YES, it share the same attribute in entire text range. - */ -- (BOOL)as_isSharedAttributesInAllRange; - -/** - If YES, it can be drawn with the [drawWithRect:options:context:] method or displayed with UIKit. - If NO, it should be drawn with CoreText or ASText. - - @discussion If the method returns NO, it means that there's at least one attribute - which is not supported by UIKit (such as CTParagraphStyleRef). If display this string - in UIKit, it may lose some attribute, or even crash the app. - */ -- (BOOL)as_canDrawWithUIKit; - -@end - - - - -/** - Set pre-defined attributes to attributed string. - All properties defined in UIKit, CoreText and ASText are included. - */ -@interface NSMutableAttributedString (ASText) - -#pragma mark - Set character attribute -///============================================================================= -/// @name Set character attribute -///============================================================================= - -/** - Sets the attributes to the entire text string. - - @discussion The old attributes will be removed. - - @param attributes A dictionary containing the attributes to set, or nil to remove all attributes. - */ -- (void)as_setAttributes:(nullable NSDictionary *)attributes; -- (void)setAs_attributes:(nullable NSDictionary *)attributes; - -/** - Sets an attribute with the given name and value to the entire text string. - - @param name A string specifying the attribute name. - @param value The attribute value associated with name. Pass `nil` or `NSNull` to - remove the attribute. - */ -- (void)as_setAttribute:(NSString *)name value:(nullable id)value; - -/** - Sets an attribute with the given name and value to the characters in the specified range. - - @param name A string specifying the attribute name. - @param value The attribute value associated with name. Pass `nil` or `NSNull` to - remove the attribute. - @param range The range of characters to which the specified attribute/value pair applies. - */ -- (void)as_setAttribute:(NSString *)name value:(nullable id)value range:(NSRange)range; - -/** - Removes all attributes in the specified range. - - @param range The range of characters. - */ -- (void)as_removeAttributesInRange:(NSRange)range; - - -#pragma mark - Set character attribute as property -///============================================================================= -/// @name Set character attribute as property -///============================================================================= - -/** - The font of the text. - - @discussion Default is Helvetica (Neue) 12. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:6.0 ASText:6.0 - */ -@property (nullable, nonatomic) UIFont *as_font; -- (void)as_setFont:(nullable UIFont *)font range:(NSRange)range; - -/** - A kerning adjustment. - - @discussion Default is standard kerning. The kerning attribute indicate how many - points the following character should be shifted from its default offset as - defined by the current character's font in points; a positive kern indicates a - shift farther along and a negative kern indicates a shift closer to the current - character. If this attribute is not present, standard kerning will be used. - If this attribute is set to 0.0, no kerning will be done at all. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:6.0 ASText:6.0 - */ -@property (nullable, nonatomic) NSNumber *as_kern; -- (void)as_setKern:(nullable NSNumber *)kern range:(NSRange)range; - -/** - The foreground color. - - @discussion Default is Black. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:6.0 ASText:6.0 - */ -@property (nullable, nonatomic) UIColor *as_color; -- (void)as_setColor:(nullable UIColor *)color range:(NSRange)range; - -/** - The background color. - - @discussion Default is nil (or no background). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since UIKit:6.0 - */ -@property (nullable, nonatomic) UIColor *as_backgroundColor; -- (void)as_setBackgroundColor:(nullable UIColor *)backgroundColor range:(NSRange)range; - -/** - The stroke width. - - @discussion Default value is 0.0 (no stroke). This attribute, interpreted as - a percentage of font point size, controls the text drawing mode: positive - values effect drawing with stroke only; negative values are for stroke and fill. - A typical value for outlined text is 3.0. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:6.0 ASText:6.0 - */ -@property (nullable, nonatomic) NSNumber *as_strokeWidth; -- (void)as_setStrokeWidth:(nullable NSNumber *)strokeWidth range:(NSRange)range; - -/** - The stroke color. - - @discussion Default value is nil (same as foreground color). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:6.0 ASText:6.0 - */ -@property (nullable, nonatomic) UIColor *as_strokeColor; -- (void)as_setStrokeColor:(nullable UIColor *)strokeColor range:(NSRange)range; - -/** - The text shadow. - - @discussion Default value is nil (no shadow). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since UIKit:6.0 ASText:6.0 - */ -@property (nullable, nonatomic) NSShadow *as_shadow; -- (void)as_setShadow:(nullable NSShadow *)shadow range:(NSRange)range; - -/** - The strikethrough style. - - @discussion Default value is NSUnderlineStyleNone (no strikethrough). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since UIKit:6.0 - */ -@property (nonatomic) NSUnderlineStyle as_strikethroughStyle; -- (void)as_setStrikethroughStyle:(NSUnderlineStyle)strikethroughStyle range:(NSRange)range; - -/** - The strikethrough color. - - @discussion Default value is nil (same as foreground color). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since UIKit:7.0 - */ -@property (nullable, nonatomic) UIColor *as_strikethroughColor; -- (void)as_setStrikethroughColor:(nullable UIColor *)strikethroughColor range:(NSRange)range NS_AVAILABLE_IOS(7_0); - -/** - The underline style. - - @discussion Default value is NSUnderlineStyleNone (no underline). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:6.0 - */ -@property (nonatomic) NSUnderlineStyle as_underlineStyle; -- (void)as_setUnderlineStyle:(NSUnderlineStyle)underlineStyle range:(NSRange)range; - -/** - The underline color. - - @discussion Default value is nil (same as foreground color). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:7.0 - */ -@property (nullable, nonatomic) UIColor *as_underlineColor; -- (void)as_setUnderlineColor:(nullable UIColor *)underlineColor range:(NSRange)range; - -/** - Ligature formation control. - - @discussion Default is int value 1. The ligature attribute determines what kinds - of ligatures should be used when displaying the string. A value of 0 indicates - that only ligatures essential for proper rendering of text should be used, - 1 indicates that standard ligatures should be used, and 2 indicates that all - available ligatures should be used. Which ligatures are standard depends on the - script and possibly the font. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:6.0 ASText:6.0 - */ -@property (nullable, nonatomic) NSNumber *as_ligature; -- (void)as_setLigature:(nullable NSNumber *)ligature range:(NSRange)range; - -/** - The text effect. - - @discussion Default is nil (no effect). The only currently supported value - is NSTextEffectLetterpressStyle. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since UIKit:7.0 - */ -@property (nullable, nonatomic) NSString *as_textEffect; -- (void)as_setTextEffect:(nullable NSString *)textEffect range:(NSRange)range NS_AVAILABLE_IOS(7_0); - -/** - The skew to be applied to glyphs. - - @discussion Default is 0 (no skew). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since UIKit:7.0 - */ -@property (nullable, nonatomic) NSNumber *as_obliqueness; -- (void)as_setObliqueness:(nullable NSNumber *)obliqueness range:(NSRange)range NS_AVAILABLE_IOS(7_0); - -/** - The log of the expansion factor to be applied to glyphs. - - @discussion Default is 0 (no expansion). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since UIKit:7.0 - */ -@property (nullable, nonatomic) NSNumber *as_expansion; -- (void)as_setExpansion:(nullable NSNumber *)expansion range:(NSRange)range NS_AVAILABLE_IOS(7_0); - -/** - The character's offset from the baseline, in points. - - @discussion Default is 0. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since UIKit:7.0 - */ -@property (nullable, nonatomic) NSNumber *as_baselineOffset; -- (void)as_setBaselineOffset:(nullable NSNumber *)baselineOffset range:(NSRange)range NS_AVAILABLE_IOS(7_0); - -/** - Glyph orientation control. - - @discussion Default is NO. A value of NO indicates that horizontal glyph forms - are to be used, YES indicates that vertical glyph forms are to be used. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:4.3 ASText:6.0 - */ -@property (nonatomic) BOOL as_verticalGlyphForm; -- (void)as_setVerticalGlyphForm:(BOOL)verticalGlyphForm range:(NSRange)range; - -/** - Specifies text language. - - @discussion Value must be a NSString containing a locale identifier. Default is - unset. When this attribute is set to a valid identifier, it will be used to select - localized glyphs (if supported by the font) and locale-specific line breaking rules. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:7.0 ASText:7.0 - */ -@property (nullable, nonatomic) NSString *as_language; -- (void)as_setLanguage:(nullable NSString *)language range:(NSRange)range NS_AVAILABLE_IOS(7_0); - -/** - Specifies a bidirectional override or embedding. - - @discussion See alse NSWritingDirection and NSWritingDirectionAttributeName. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:7.0 ASText:6.0 - */ -@property (nullable, nonatomic) NSArray *as_writingDirection; -- (void)as_setWritingDirection:(nullable NSArray *)writingDirection range:(NSRange)range; - -/** - An NSParagraphStyle object which is used to specify things like - line alignment, tab rulers, writing direction, etc. - - @discussion Default is nil ([NSParagraphStyle defaultParagraphStyle]). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nullable, nonatomic) NSParagraphStyle *as_paragraphStyle; -- (void)as_setParagraphStyle:(nullable NSParagraphStyle *)paragraphStyle range:(NSRange)range; - - -#pragma mark - Set paragraph attribute as property -///============================================================================= -/// @name Set paragraph attribute as property -///============================================================================= - -/** - The text alignment (A wrapper for NSParagraphStyle). - - @discussion Natural text alignment is realized as left or right alignment - depending on the line sweep direction of the first script contained in the paragraph. - @discussion Default is NSTextAlignmentNatural. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic) NSTextAlignment as_alignment; -- (void)as_setAlignment:(NSTextAlignment)alignment range:(NSRange)range; - -/** - The mode that should be used to break lines (A wrapper for NSParagraphStyle). - - @discussion This property contains the line break mode to be used laying out the paragraph's text. - @discussion Default is NSLineBreakByWordWrapping. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic) NSLineBreakMode as_lineBreakMode; -- (void)as_setLineBreakMode:(NSLineBreakMode)lineBreakMode range:(NSRange)range; - -/** - The distance in points between the bottom of one line fragment and the top of the next. - (A wrapper for NSParagraphStyle) - - @discussion This value is always nonnegative. This value is included in the line - fragment heights in the layout manager. - @discussion Default is 0. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic) CGFloat as_lineSpacing; -- (void)as_setLineSpacing:(CGFloat)lineSpacing range:(NSRange)range; - -/** - The space after the end of the paragraph (A wrapper for NSParagraphStyle). - - @discussion This property contains the space (measured in points) added at the - end of the paragraph to separate it from the following paragraph. This value must - be nonnegative. The space between paragraphs is determined by adding the previous - paragraph's paragraphSpacing and the current paragraph's paragraphSpacingBefore. - @discussion Default is 0. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic) CGFloat as_paragraphSpacing; -- (void)as_setParagraphSpacing:(CGFloat)paragraphSpacing range:(NSRange)range; - -/** - The distance between the paragraph's top and the beginning of its text content. - (A wrapper for NSParagraphStyle). - - @discussion This property contains the space (measured in points) between the - paragraph's top and the beginning of its text content. - @discussion Default is 0. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic) CGFloat as_paragraphSpacingBefore; -- (void)as_setParagraphSpacingBefore:(CGFloat)paragraphSpacingBefore range:(NSRange)range; - -/** - The indentation of the first line (A wrapper for NSParagraphStyle). - - @discussion This property contains the distance (in points) from the leading margin - of a text container to the beginning of the paragraph's first line. This value - is always nonnegative. - @discussion Default is 0. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic) CGFloat as_firstLineHeadIndent; -- (void)as_setFirstLineHeadIndent:(CGFloat)firstLineHeadIndent range:(NSRange)range; - -/** - The indentation of the receiver's lines other than the first. (A wrapper for NSParagraphStyle). - - @discussion This property contains the distance (in points) from the leading margin - of a text container to the beginning of lines other than the first. This value is - always nonnegative. - @discussion Default is 0. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic) CGFloat as_headIndent; -- (void)as_setHeadIndent:(CGFloat)headIndent range:(NSRange)range; - -/** - The trailing indentation (A wrapper for NSParagraphStyle). - - @discussion If positive, this value is the distance from the leading margin - (for example, the left margin in left-to-right text). If 0 or negative, it's the - distance from the trailing margin. - @discussion Default is 0. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic) CGFloat as_tailIndent; -- (void)as_setTailIndent:(CGFloat)tailIndent range:(NSRange)range; - -/** - The receiver's minimum height (A wrapper for NSParagraphStyle). - - @discussion This property contains the minimum height in points that any line in - the receiver will occupy, regardless of the font size or size of any attached graphic. - This value must be nonnegative. - @discussion Default is 0. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic) CGFloat as_minimumLineHeight; -- (void)as_setMinimumLineHeight:(CGFloat)minimumLineHeight range:(NSRange)range; - -/** - The receiver's maximum line height (A wrapper for NSParagraphStyle). - - @discussion This property contains the maximum height in points that any line in - the receiver will occupy, regardless of the font size or size of any attached graphic. - This value is always nonnegative. Glyphs and graphics exceeding this height will - overlap neighboring lines; however, a maximum height of 0 implies no line height limit. - Although this limit applies to the line itself, line spacing adds extra space between adjacent lines. - @discussion Default is 0 (no limit). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic) CGFloat as_maximumLineHeight; -- (void)as_setMaximumLineHeight:(CGFloat)maximumLineHeight range:(NSRange)range; - -/** - The line height multiple (A wrapper for NSParagraphStyle). - - @discussion This property contains the line break mode to be used laying out the paragraph's text. - @discussion Default is 0 (no multiple). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic) CGFloat as_lineHeightMultiple; -- (void)as_setLineHeightMultiple:(CGFloat)lineHeightMultiple range:(NSRange)range; - -/** - The base writing direction (A wrapper for NSParagraphStyle). - - @discussion If you specify NSWritingDirectionNaturalDirection, the receiver resolves - the writing direction to either NSWritingDirectionLeftToRight or NSWritingDirectionRightToLeft, - depending on the direction for the user's `language` preference setting. - @discussion Default is NSWritingDirectionNatural. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic) NSWritingDirection as_baseWritingDirection; -- (void)as_setBaseWritingDirection:(NSWritingDirection)baseWritingDirection range:(NSRange)range; - -/** - The paragraph's threshold for hyphenation. (A wrapper for NSParagraphStyle). - - @discussion Valid values lie between 0.0 and 1.0 inclusive. Hyphenation is attempted - when the ratio of the text width (as broken without hyphenation) to the width of the - line fragment is less than the hyphenation factor. When the paragraph's hyphenation - factor is 0.0, the layout manager's hyphenation factor is used instead. When both - are 0.0, hyphenation is disabled. - @discussion Default is 0. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since UIKit:6.0 - */ -@property (nonatomic) float as_hyphenationFactor; -- (void)as_setHyphenationFactor:(float)hyphenationFactor range:(NSRange)range; - -/** - The document-wide default tab interval (A wrapper for NSParagraphStyle). - - @discussion This property represents the default tab interval in points. Tabs after the - last specified in tabStops are placed at integer multiples of this distance (if positive). - @discussion Default is 0. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:7.0 UIKit:7.0 ASText:7.0 - */ -@property (nonatomic) CGFloat as_defaultTabInterval; -- (void)as_setDefaultTabInterval:(CGFloat)defaultTabInterval range:(NSRange)range NS_AVAILABLE_IOS(7_0); - -/** - An array of NSTextTab objects representing the receiver's tab stops. - (A wrapper for NSParagraphStyle). - - @discussion The NSTextTab objects, sorted by location, define the tab stops for - the paragraph style. - @discussion Default is 12 TabStops with 28.0 tab interval. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:7.0 UIKit:7.0 ASText:7.0 - */ -@property (nullable, nonatomic, copy) NSArray *as_tabStops; -- (void)as_setTabStops:(nullable NSArray *)tabStops range:(NSRange)range NS_AVAILABLE_IOS(7_0); - -#pragma mark - Set ASText attribute as property -///============================================================================= -/// @name Set ASText attribute as property -///============================================================================= - -/** - The text shadow. - - @discussion Default value is nil (no shadow). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since ASText:6.0 - */ -@property (nullable, nonatomic) ASTextShadow *as_textShadow; -- (void)as_setTextShadow:(nullable ASTextShadow *)textShadow range:(NSRange)range; - -/** - The text inner shadow. - - @discussion Default value is nil (no shadow). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since ASText:6.0 - */ -@property (nullable, nonatomic) ASTextShadow *as_textInnerShadow; -- (void)as_setTextInnerShadow:(nullable ASTextShadow *)textInnerShadow range:(NSRange)range; - -/** - The text underline. - - @discussion Default value is nil (no underline). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since ASText:6.0 - */ -@property (nullable, nonatomic) ASTextDecoration *as_textUnderline; -- (void)as_setTextUnderline:(nullable ASTextDecoration *)textUnderline range:(NSRange)range; - -/** - The text strikethrough. - - @discussion Default value is nil (no strikethrough). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since ASText:6.0 - */ -@property (nullable, nonatomic) ASTextDecoration *as_textStrikethrough; -- (void)as_setTextStrikethrough:(nullable ASTextDecoration *)textStrikethrough range:(NSRange)range; - -/** - The text border. - - @discussion Default value is nil (no border). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since ASText:6.0 - */ -@property (nullable, nonatomic) ASTextBorder *as_textBorder; -- (void)as_setTextBorder:(nullable ASTextBorder *)textBorder range:(NSRange)range; - -/** - The text background border. - - @discussion Default value is nil (no background border). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since ASText:6.0 - */ -@property (nullable, nonatomic) ASTextBorder *as_textBackgroundBorder; -- (void)as_setTextBackgroundBorder:(nullable ASTextBorder *)textBackgroundBorder range:(NSRange)range; - -/** - The glyph transform. - - @discussion Default value is CGAffineTransformIdentity (no transform). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since ASText:6.0 - */ -@property (nonatomic) CGAffineTransform as_textGlyphTransform; -- (void)as_setTextGlyphTransform:(CGAffineTransform)textGlyphTransform range:(NSRange)range; - - -#pragma mark - Set discontinuous attribute for range -///============================================================================= -/// @name Set discontinuous attribute for range -///============================================================================= - -- (void)as_setSuperscript:(nullable NSNumber *)superscript range:(NSRange)range; -- (void)as_setGlyphInfo:(nullable CTGlyphInfoRef)glyphInfo range:(NSRange)range; -- (void)as_setCharacterShape:(nullable NSNumber *)characterShape range:(NSRange)range __TVOS_PROHIBITED; -- (void)as_setRunDelegate:(nullable CTRunDelegateRef)runDelegate range:(NSRange)range; -- (void)as_setBaselineClass:(nullable CFStringRef)baselineClass range:(NSRange)range; -- (void)as_setBaselineInfo:(nullable CFDictionaryRef)baselineInfo range:(NSRange)range; -- (void)as_setBaselineReferenceInfo:(nullable CFDictionaryRef)referenceInfo range:(NSRange)range; -- (void)as_setRubyAnnotation:(nullable CTRubyAnnotationRef)ruby range:(NSRange)range NS_AVAILABLE_IOS(8_0); -- (void)as_setAttachment:(nullable NSTextAttachment *)attachment range:(NSRange)range NS_AVAILABLE_IOS(7_0); -- (void)as_setLink:(nullable id)link range:(NSRange)range NS_AVAILABLE_IOS(7_0); -- (void)as_setTextBackedString:(nullable ASTextBackedString *)textBackedString range:(NSRange)range; -- (void)as_setTextBinding:(nullable ASTextBinding *)textBinding range:(NSRange)range; -- (void)as_setTextAttachment:(nullable ASTextAttachment *)textAttachment range:(NSRange)range; -- (void)as_setTextHighlight:(nullable ASTextHighlight *)textHighlight range:(NSRange)range; -- (void)as_setTextBlockBorder:(nullable ASTextBorder *)textBlockBorder range:(NSRange)range; - - -#pragma mark - Convenience methods for text highlight -///============================================================================= -/// @name Convenience methods for text highlight -///============================================================================= - -/** - Convenience method to set text highlight - - @param range text range - @param color text color (pass nil to ignore) - @param backgroundColor text background color when highlight - @param userInfo user information dictionary (pass nil to ignore) - @param tapAction tap action when user tap the highlight (pass nil to ignore) - @param longPressAction long press action when user long press the highlight (pass nil to ignore) - */ -- (void)as_setTextHighlightRange:(NSRange)range - color:(nullable UIColor *)color - backgroundColor:(nullable UIColor *)backgroundColor - userInfo:(nullable NSDictionary *)userInfo - tapAction:(nullable ASTextAction)tapAction - longPressAction:(nullable ASTextAction)longPressAction; - -/** - Convenience method to set text highlight - - @param range text range - @param color text color (pass nil to ignore) - @param backgroundColor text background color when highlight - @param tapAction tap action when user tap the highlight (pass nil to ignore) - */ -- (void)as_setTextHighlightRange:(NSRange)range - color:(nullable UIColor *)color - backgroundColor:(nullable UIColor *)backgroundColor - tapAction:(nullable ASTextAction)tapAction; - -/** - Convenience method to set text highlight - - @param range text range - @param color text color (pass nil to ignore) - @param backgroundColor text background color when highlight - @param userInfo tap action when user tap the highlight (pass nil to ignore) - */ -- (void)as_setTextHighlightRange:(NSRange)range - color:(nullable UIColor *)color - backgroundColor:(nullable UIColor *)backgroundColor - userInfo:(nullable NSDictionary *)userInfo; - -#pragma mark - Utilities -///============================================================================= -/// @name Utilities -///============================================================================= - -/** - Inserts into the receiver the characters of a given string at a given location. - The new string inherit the attributes of the first replaced character from location. - - @param string The string to insert into the receiver, must not be nil. - @param location The location at which string is inserted. The location must not - exceed the bounds of the receiver. - @throw Raises an NSRangeException if the location out of bounds. - */ -- (void)as_insertString:(NSString *)string atIndex:(NSUInteger)location; - -/** - Adds to the end of the receiver the characters of a given string. - The new string inherit the attributes of the receiver's tail. - - @param string The string to append to the receiver, must not be nil. - */ -- (void)as_appendString:(NSString *)string; - -/** - Removes all discontinuous attributes in a specified range. - See `allDiscontinuousAttributeKeys`. - - @param range A text range. - */ -- (void)as_removeDiscontinuousAttributesInRange:(NSRange)range; - -/** - Returns all discontinuous attribute keys, such as RunDelegate/Attachment/Ruby. - - @discussion These attributes can only set to a specified range of text, and - should not extend to other range when editing text. - */ -+ (NSArray *)as_allDiscontinuousAttributeKeys; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.mm b/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.mm deleted file mode 100644 index 39f628151c..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.mm +++ /dev/null @@ -1,1208 +0,0 @@ -// -// NSAttributedString+ASText.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import -#import -#import - - -// Dummy class for category -@interface NSAttributedString_ASText : NSObject @end -@implementation NSAttributedString_ASText @end - - -@implementation NSAttributedString (ASText) - -- (NSDictionary *)as_attributesAtIndex:(NSUInteger)index { - if (index > self.length || self.length == 0) return nil; - if (self.length > 0 && index == self.length) index--; - return [self attributesAtIndex:index effectiveRange:NULL]; -} - -- (id)as_attribute:(NSString *)attributeName atIndex:(NSUInteger)index { - if (!attributeName) return nil; - if (index > self.length || self.length == 0) return nil; - if (self.length > 0 && index == self.length) index--; - return [self attribute:attributeName atIndex:index effectiveRange:NULL]; -} - -- (NSDictionary *)as_attributes { - return [self as_attributesAtIndex:0]; -} - -- (UIFont *)as_font { - return [self as_fontAtIndex:0]; -} - -- (UIFont *)as_fontAtIndex:(NSUInteger)index { - return [self as_attribute:NSFontAttributeName atIndex:index]; -} - -- (NSNumber *)as_kern { - return [self as_kernAtIndex:0]; -} - -- (NSNumber *)as_kernAtIndex:(NSUInteger)index { - return [self as_attribute:NSKernAttributeName atIndex:index]; -} - -- (UIColor *)as_color { - return [self as_colorAtIndex:0]; -} - -- (UIColor *)as_colorAtIndex:(NSUInteger)index { - UIColor *color = [self as_attribute:NSForegroundColorAttributeName atIndex:index]; - if (!color) { - CGColorRef ref = (__bridge CGColorRef)([self as_attribute:(NSString *)kCTForegroundColorAttributeName atIndex:index]); - color = [UIColor colorWithCGColor:ref]; - } - if (color && ![color isKindOfClass:[UIColor class]]) { - if (CFGetTypeID((__bridge CFTypeRef)(color)) == CGColorGetTypeID()) { - color = [UIColor colorWithCGColor:(__bridge CGColorRef)(color)]; - } else { - color = nil; - } - } - return color; -} - -- (UIColor *)as_backgroundColor { - return [self as_backgroundColorAtIndex:0]; -} - -- (UIColor *)as_backgroundColorAtIndex:(NSUInteger)index { - return [self as_attribute:NSBackgroundColorAttributeName atIndex:index]; -} - -- (NSNumber *)as_strokeWidth { - return [self as_strokeWidthAtIndex:0]; -} - -- (NSNumber *)as_strokeWidthAtIndex:(NSUInteger)index { - return [self as_attribute:NSStrokeWidthAttributeName atIndex:index]; -} - -- (UIColor *)as_strokeColor { - return [self as_strokeColorAtIndex:0]; -} - -- (UIColor *)as_strokeColorAtIndex:(NSUInteger)index { - UIColor *color = [self as_attribute:NSStrokeColorAttributeName atIndex:index]; - if (!color) { - CGColorRef ref = (__bridge CGColorRef)([self as_attribute:(NSString *)kCTStrokeColorAttributeName atIndex:index]); - color = [UIColor colorWithCGColor:ref]; - } - return color; -} - -- (NSShadow *)as_shadow { - return [self as_shadowAtIndex:0]; -} - -- (NSShadow *)as_shadowAtIndex:(NSUInteger)index { - return [self as_attribute:NSShadowAttributeName atIndex:index]; -} - -- (NSUnderlineStyle)as_strikethroughStyle { - return [self as_strikethroughStyleAtIndex:0]; -} - -- (NSUnderlineStyle)as_strikethroughStyleAtIndex:(NSUInteger)index { - NSNumber *style = [self as_attribute:NSStrikethroughStyleAttributeName atIndex:index]; - return (NSUnderlineStyle)style.integerValue; -} - -- (UIColor *)as_strikethroughColor { - return [self as_strikethroughColorAtIndex:0]; -} - -- (UIColor *)as_strikethroughColorAtIndex:(NSUInteger)index { - return [self as_attribute:NSStrikethroughColorAttributeName atIndex:index]; -} - -- (NSUnderlineStyle)as_underlineStyle { - return [self as_underlineStyleAtIndex:0]; -} - -- (NSUnderlineStyle)as_underlineStyleAtIndex:(NSUInteger)index { - NSNumber *style = [self as_attribute:NSUnderlineStyleAttributeName atIndex:index]; - return (NSUnderlineStyle)style.integerValue; -} - -- (UIColor *)as_underlineColor { - return [self as_underlineColorAtIndex:0]; -} - -- (UIColor *)as_underlineColorAtIndex:(NSUInteger)index { - UIColor *color = [self as_attribute:NSUnderlineColorAttributeName atIndex:index]; - if (!color) { - CGColorRef ref = (__bridge CGColorRef)([self as_attribute:(NSString *)kCTUnderlineColorAttributeName atIndex:index]); - color = [UIColor colorWithCGColor:ref]; - } - return color; -} - -- (NSNumber *)as_ligature { - return [self as_ligatureAtIndex:0]; -} - -- (NSNumber *)as_ligatureAtIndex:(NSUInteger)index { - return [self as_attribute:NSLigatureAttributeName atIndex:index]; -} - -- (NSString *)as_textEffect { - return [self as_textEffectAtIndex:0]; -} - -- (NSString *)as_textEffectAtIndex:(NSUInteger)index { - return [self as_attribute:NSTextEffectAttributeName atIndex:index]; -} - -- (NSNumber *)as_obliqueness { - return [self as_obliquenessAtIndex:0]; -} - -- (NSNumber *)as_obliquenessAtIndex:(NSUInteger)index { - return [self as_attribute:NSObliquenessAttributeName atIndex:index]; -} - -- (NSNumber *)as_expansion { - return [self as_expansionAtIndex:0]; -} - -- (NSNumber *)as_expansionAtIndex:(NSUInteger)index { - return [self as_attribute:NSExpansionAttributeName atIndex:index]; -} - -- (NSNumber *)as_baselineOffset { - return [self as_baselineOffsetAtIndex:0]; -} - -- (NSNumber *)as_baselineOffsetAtIndex:(NSUInteger)index { - return [self as_attribute:NSBaselineOffsetAttributeName atIndex:index]; -} - -- (BOOL)as_verticalGlyphForm { - return [self as_verticalGlyphFormAtIndex:0]; -} - -- (BOOL)as_verticalGlyphFormAtIndex:(NSUInteger)index { - NSNumber *num = [self as_attribute:NSVerticalGlyphFormAttributeName atIndex:index]; - return num.boolValue; -} - -- (NSString *)as_language { - return [self as_languageAtIndex:0]; -} - -- (NSString *)as_languageAtIndex:(NSUInteger)index { - return [self as_attribute:(id)kCTLanguageAttributeName atIndex:index]; -} - -- (NSArray *)as_writingDirection { - return [self as_writingDirectionAtIndex:0]; -} - -- (NSArray *)as_writingDirectionAtIndex:(NSUInteger)index { - return [self as_attribute:(id)kCTWritingDirectionAttributeName atIndex:index]; -} - -- (NSParagraphStyle *)as_paragraphStyle { - return [self as_paragraphStyleAtIndex:0]; -} - -- (NSParagraphStyle *)as_paragraphStyleAtIndex:(NSUInteger)index { - /* - NSParagraphStyle is NOT toll-free bridged to CTParagraphStyleRef. - - CoreText can use both NSParagraphStyle and CTParagraphStyleRef, - but UILabel/UITextView can only use NSParagraphStyle. - - We use NSParagraphStyle in both CoreText and UIKit. - */ - NSParagraphStyle *style = [self as_attribute:NSParagraphStyleAttributeName atIndex:index]; - if (style) { - if (CFGetTypeID((__bridge CFTypeRef)(style)) == CTParagraphStyleGetTypeID()) { \ - style = [NSParagraphStyle as_styleWithCTStyle:(__bridge CTParagraphStyleRef)(style)]; - } - } - return style; -} - -#define ParagraphAttribute(_attr_) \ -NSParagraphStyle *style = self.as_paragraphStyle; \ -if (!style) style = [NSParagraphStyle defaultParagraphStyle]; \ -return style. _attr_; - -#define ParagraphAttributeAtIndex(_attr_) \ -NSParagraphStyle *style = [self as_paragraphStyleAtIndex:index]; \ -if (!style) style = [NSParagraphStyle defaultParagraphStyle]; \ -return style. _attr_; - -- (NSTextAlignment)as_alignment { - ParagraphAttribute(alignment); -} - -- (NSLineBreakMode)as_lineBreakMode { - ParagraphAttribute(lineBreakMode); -} - -- (CGFloat)as_lineSpacing { - ParagraphAttribute(lineSpacing); -} - -- (CGFloat)as_paragraphSpacing { - ParagraphAttribute(paragraphSpacing); -} - -- (CGFloat)as_paragraphSpacingBefore { - ParagraphAttribute(paragraphSpacingBefore); -} - -- (CGFloat)as_firstLineHeadIndent { - ParagraphAttribute(firstLineHeadIndent); -} - -- (CGFloat)as_headIndent { - ParagraphAttribute(headIndent); -} - -- (CGFloat)as_tailIndent { - ParagraphAttribute(tailIndent); -} - -- (CGFloat)as_minimumLineHeight { - ParagraphAttribute(minimumLineHeight); -} - -- (CGFloat)as_maximumLineHeight { - ParagraphAttribute(maximumLineHeight); -} - -- (CGFloat)as_lineHeightMultiple { - ParagraphAttribute(lineHeightMultiple); -} - -- (NSWritingDirection)as_baseWritingDirection { - ParagraphAttribute(baseWritingDirection); -} - -- (float)as_hyphenationFactor { - ParagraphAttribute(hyphenationFactor); -} - -- (CGFloat)as_defaultTabInterval { - ParagraphAttribute(defaultTabInterval); -} - -- (NSArray *)as_tabStops { - ParagraphAttribute(tabStops); -} - -- (NSTextAlignment)as_alignmentAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(alignment); -} - -- (NSLineBreakMode)as_lineBreakModeAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(lineBreakMode); -} - -- (CGFloat)as_lineSpacingAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(lineSpacing); -} - -- (CGFloat)as_paragraphSpacingAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(paragraphSpacing); -} - -- (CGFloat)as_paragraphSpacingBeforeAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(paragraphSpacingBefore); -} - -- (CGFloat)as_firstLineHeadIndentAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(firstLineHeadIndent); -} - -- (CGFloat)as_headIndentAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(headIndent); -} - -- (CGFloat)as_tailIndentAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(tailIndent); -} - -- (CGFloat)as_minimumLineHeightAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(minimumLineHeight); -} - -- (CGFloat)as_maximumLineHeightAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(maximumLineHeight); -} - -- (CGFloat)as_lineHeightMultipleAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(lineHeightMultiple); -} - -- (NSWritingDirection)as_baseWritingDirectionAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(baseWritingDirection); -} - -- (float)as_hyphenationFactorAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(hyphenationFactor); -} - -- (CGFloat)as_defaultTabIntervalAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(defaultTabInterval); -} - -- (NSArray *)as_tabStopsAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(tabStops); -} - -#undef ParagraphAttribute -#undef ParagraphAttributeAtIndex - -- (ASTextShadow *)as_textShadow { - return [self as_textShadowAtIndex:0]; -} - -- (ASTextShadow *)as_textShadowAtIndex:(NSUInteger)index { - return [self as_attribute:ASTextShadowAttributeName atIndex:index]; -} - -- (ASTextShadow *)as_textInnerShadow { - return [self as_textInnerShadowAtIndex:0]; -} - -- (ASTextShadow *)as_textInnerShadowAtIndex:(NSUInteger)index { - return [self as_attribute:ASTextInnerShadowAttributeName atIndex:index]; -} - -- (ASTextDecoration *)as_textUnderline { - return [self as_textUnderlineAtIndex:0]; -} - -- (ASTextDecoration *)as_textUnderlineAtIndex:(NSUInteger)index { - return [self as_attribute:ASTextUnderlineAttributeName atIndex:index]; -} - -- (ASTextDecoration *)as_textStrikethrough { - return [self as_textStrikethroughAtIndex:0]; -} - -- (ASTextDecoration *)as_textStrikethroughAtIndex:(NSUInteger)index { - return [self as_attribute:ASTextStrikethroughAttributeName atIndex:index]; -} - -- (ASTextBorder *)as_textBorder { - return [self as_textBorderAtIndex:0]; -} - -- (ASTextBorder *)as_textBorderAtIndex:(NSUInteger)index { - return [self as_attribute:ASTextBorderAttributeName atIndex:index]; -} - -- (ASTextBorder *)as_textBackgroundBorder { - return [self as_textBackgroundBorderAtIndex:0]; -} - -- (ASTextBorder *)as_textBackgroundBorderAtIndex:(NSUInteger)index { - return [self as_attribute:ASTextBackedStringAttributeName atIndex:index]; -} - -- (CGAffineTransform)as_textGlyphTransform { - return [self as_textGlyphTransformAtIndex:0]; -} - -- (CGAffineTransform)as_textGlyphTransformAtIndex:(NSUInteger)index { - NSValue *value = [self as_attribute:ASTextGlyphTransformAttributeName atIndex:index]; - if (!value) return CGAffineTransformIdentity; - return [value CGAffineTransformValue]; -} - -- (NSString *)as_plainTextForRange:(NSRange)range { - if (range.location == NSNotFound ||range.length == NSNotFound) return nil; - NSMutableString *result = [NSMutableString string]; - if (range.length == 0) return result; - NSString *string = self.string; - [self enumerateAttribute:ASTextBackedStringAttributeName inRange:range options:kNilOptions usingBlock:^(id value, NSRange range, BOOL *stop) { - ASTextBackedString *backed = value; - if (backed && backed.string) { - [result appendString:backed.string]; - } else { - [result appendString:[string substringWithRange:range]]; - } - }]; - return result; -} - -+ (NSMutableAttributedString *)as_attachmentStringWithContent:(id)content - contentMode:(UIViewContentMode)contentMode - width:(CGFloat)width - ascent:(CGFloat)ascent - descent:(CGFloat)descent { - NSMutableAttributedString *atr = [[NSMutableAttributedString alloc] initWithString:ASTextAttachmentToken]; - - ASTextAttachment *attach = [ASTextAttachment new]; - attach.content = content; - attach.contentMode = contentMode; - [atr as_setTextAttachment:attach range:NSMakeRange(0, atr.length)]; - - ASTextRunDelegate *delegate = [ASTextRunDelegate new]; - delegate.width = width; - delegate.ascent = ascent; - delegate.descent = descent; - CTRunDelegateRef delegateRef = delegate.CTRunDelegate; - [atr as_setRunDelegate:delegateRef range:NSMakeRange(0, atr.length)]; - if (delegate) CFRelease(delegateRef); - - return atr; -} - -+ (NSMutableAttributedString *)as_attachmentStringWithContent:(id)content - contentMode:(UIViewContentMode)contentMode - attachmentSize:(CGSize)attachmentSize - alignToFont:(UIFont *)font - alignment:(ASTextVerticalAlignment)alignment { - NSMutableAttributedString *atr = [[NSMutableAttributedString alloc] initWithString:ASTextAttachmentToken]; - - ASTextAttachment *attach = [ASTextAttachment new]; - attach.content = content; - attach.contentMode = contentMode; - [atr as_setTextAttachment:attach range:NSMakeRange(0, atr.length)]; - - ASTextRunDelegate *delegate = [ASTextRunDelegate new]; - delegate.width = attachmentSize.width; - switch (alignment) { - case ASTextVerticalAlignmentTop: { - delegate.ascent = font.ascender; - delegate.descent = attachmentSize.height - font.ascender; - if (delegate.descent < 0) { - delegate.descent = 0; - delegate.ascent = attachmentSize.height; - } - } break; - case ASTextVerticalAlignmentCenter: { - CGFloat fontHeight = font.ascender - font.descender; - CGFloat yOffset = font.ascender - fontHeight * 0.5; - delegate.ascent = attachmentSize.height * 0.5 + yOffset; - delegate.descent = attachmentSize.height - delegate.ascent; - if (delegate.descent < 0) { - delegate.descent = 0; - delegate.ascent = attachmentSize.height; - } - } break; - case ASTextVerticalAlignmentBottom: { - delegate.ascent = attachmentSize.height + font.descender; - delegate.descent = -font.descender; - if (delegate.ascent < 0) { - delegate.ascent = 0; - delegate.descent = attachmentSize.height; - } - } break; - default: { - delegate.ascent = attachmentSize.height; - delegate.descent = 0; - } break; - } - - CTRunDelegateRef delegateRef = delegate.CTRunDelegate; - [atr as_setRunDelegate:delegateRef range:NSMakeRange(0, atr.length)]; - if (delegate) CFRelease(delegateRef); - - return atr; -} - -+ (NSMutableAttributedString *)as_attachmentStringWithEmojiImage:(UIImage *)image - fontSize:(CGFloat)fontSize { - if (!image || fontSize <= 0) return nil; - - BOOL hasAnim = NO; - if (image.images.count > 1) { - hasAnim = YES; - } else if (NSProtocolFromString(@"ASAnimatedImage") && - [image conformsToProtocol:NSProtocolFromString(@"ASAnimatedImage")]) { - NSNumber *frameCount = [image valueForKey:@"animatedImageFrameCount"]; - if (frameCount.intValue > 1) hasAnim = YES; - } - - CGFloat ascent = ASTextEmojiGetAscentWithFontSize(fontSize); - CGFloat descent = ASTextEmojiGetDescentWithFontSize(fontSize); - CGRect bounding = ASTextEmojiGetGlyphBoundingRectWithFontSize(fontSize); - - ASTextRunDelegate *delegate = [ASTextRunDelegate new]; - delegate.ascent = ascent; - delegate.descent = descent; - delegate.width = bounding.size.width + 2 * bounding.origin.x; - - ASTextAttachment *attachment = [ASTextAttachment new]; - attachment.contentMode = UIViewContentModeScaleAspectFit; - attachment.contentInsets = UIEdgeInsetsMake(ascent - (bounding.size.height + bounding.origin.y), bounding.origin.x, descent + bounding.origin.y, bounding.origin.x); - if (hasAnim) { - Class imageClass = NSClassFromString(@"ASAnimatedImageView"); - if (!imageClass) imageClass = [UIImageView class]; - UIImageView *view = (id)[imageClass new]; - view.frame = bounding; - view.image = image; - view.contentMode = UIViewContentModeScaleAspectFit; - attachment.content = view; - } else { - attachment.content = image; - } - - NSMutableAttributedString *atr = [[NSMutableAttributedString alloc] initWithString:ASTextAttachmentToken]; - [atr as_setTextAttachment:attachment range:NSMakeRange(0, atr.length)]; - CTRunDelegateRef ctDelegate = delegate.CTRunDelegate; - [atr as_setRunDelegate:ctDelegate range:NSMakeRange(0, atr.length)]; - if (ctDelegate) CFRelease(ctDelegate); - - return atr; -} - -- (NSRange)as_rangeOfAll { - return NSMakeRange(0, self.length); -} - -- (BOOL)as_isSharedAttributesInAllRange { - __block BOOL shared = YES; - __block NSDictionary *firstAttrs = nil; - [self enumerateAttributesInRange:self.as_rangeOfAll options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) { - if (range.location == 0) { - firstAttrs = attrs; - } else { - if (firstAttrs.count != attrs.count) { - shared = NO; - *stop = YES; - } else if (firstAttrs) { - if (![firstAttrs isEqualToDictionary:attrs]) { - shared = NO; - *stop = YES; - } - } - } - }]; - return shared; -} - -- (BOOL)as_canDrawWithUIKit { - static NSMutableSet *failSet; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - failSet = [NSMutableSet new]; - [failSet addObject:(id)kCTGlyphInfoAttributeName]; -#if TARGET_OS_IOS -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [failSet addObject:(id)kCTCharacterShapeAttributeName]; -#pragma clang diagnostic pop -#endif - [failSet addObject:(id)kCTLanguageAttributeName]; - [failSet addObject:(id)kCTRunDelegateAttributeName]; - [failSet addObject:(id)kCTBaselineClassAttributeName]; - [failSet addObject:(id)kCTBaselineInfoAttributeName]; - [failSet addObject:(id)kCTBaselineReferenceInfoAttributeName]; - [failSet addObject:(id)kCTRubyAnnotationAttributeName]; - [failSet addObject:ASTextShadowAttributeName]; - [failSet addObject:ASTextInnerShadowAttributeName]; - [failSet addObject:ASTextUnderlineAttributeName]; - [failSet addObject:ASTextStrikethroughAttributeName]; - [failSet addObject:ASTextBorderAttributeName]; - [failSet addObject:ASTextBackgroundBorderAttributeName]; - [failSet addObject:ASTextBlockBorderAttributeName]; - [failSet addObject:ASTextAttachmentAttributeName]; - [failSet addObject:ASTextHighlightAttributeName]; - [failSet addObject:ASTextGlyphTransformAttributeName]; - }); - -#define Fail { result = NO; *stop = YES; return; } - __block BOOL result = YES; - [self enumerateAttributesInRange:self.as_rangeOfAll options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) { - if (attrs.count == 0) return; - for (NSString *str in attrs.allKeys) { - if ([failSet containsObject:str]) Fail; - } - if (attrs[(id)kCTForegroundColorAttributeName] && !attrs[NSForegroundColorAttributeName]) Fail; - if (attrs[(id)kCTStrokeColorAttributeName] && !attrs[NSStrokeColorAttributeName]) Fail; - if (attrs[(id)kCTUnderlineColorAttributeName]) { - if (!attrs[NSUnderlineColorAttributeName]) Fail; - } - NSParagraphStyle *style = attrs[NSParagraphStyleAttributeName]; - if (style && CFGetTypeID((__bridge CFTypeRef)(style)) == CTParagraphStyleGetTypeID()) Fail; - }]; - return result; -#undef Fail -} - -@end - -@implementation NSMutableAttributedString (ASText) - -- (void)as_setAttributes:(NSDictionary *)attributes { - [self setAs_attributes:attributes]; -} - -- (void)setAs_attributes:(NSDictionary *)attributes { - if (attributes == (id)[NSNull null]) attributes = nil; - [self setAttributes:@{} range:NSMakeRange(0, self.length)]; - [attributes enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { - [self as_setAttribute:key value:obj]; - }]; -} - -- (void)as_setAttribute:(NSString *)name value:(id)value { - [self as_setAttribute:name value:value range:NSMakeRange(0, self.length)]; -} - -- (void)as_setAttribute:(NSString *)name value:(id)value range:(NSRange)range { - if (!name || [NSNull isEqual:name]) return; - if (value && ![NSNull isEqual:value]) [self addAttribute:name value:value range:range]; - else [self removeAttribute:name range:range]; -} - -- (void)as_removeAttributesInRange:(NSRange)range { - [self setAttributes:nil range:range]; -} - -#pragma mark - Property Setter - -- (void)setAs_font:(UIFont *)font { - /* - In iOS7 and later, UIFont is toll-free bridged to CTFontRef, - although Apple does not mention it in documentation. - - In iOS6, UIFont is a wrapper for CTFontRef, so CoreText can alse use UIfont, - but UILabel/UITextView cannot use CTFontRef. - - We use UIFont for both CoreText and UIKit. - */ - [self as_setFont:font range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_kern:(NSNumber *)kern { - [self as_setKern:kern range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_color:(UIColor *)color { - [self as_setColor:color range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_backgroundColor:(UIColor *)backgroundColor { - [self as_setBackgroundColor:backgroundColor range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_strokeWidth:(NSNumber *)strokeWidth { - [self as_setStrokeWidth:strokeWidth range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_strokeColor:(UIColor *)strokeColor { - [self as_setStrokeColor:strokeColor range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_shadow:(NSShadow *)shadow { - [self as_setShadow:shadow range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_strikethroughStyle:(NSUnderlineStyle)strikethroughStyle { - [self as_setStrikethroughStyle:strikethroughStyle range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_strikethroughColor:(UIColor *)strikethroughColor { - [self as_setStrikethroughColor:strikethroughColor range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_underlineStyle:(NSUnderlineStyle)underlineStyle { - [self as_setUnderlineStyle:underlineStyle range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_underlineColor:(UIColor *)underlineColor { - [self as_setUnderlineColor:underlineColor range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_ligature:(NSNumber *)ligature { - [self as_setLigature:ligature range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_textEffect:(NSString *)textEffect { - [self as_setTextEffect:textEffect range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_obliqueness:(NSNumber *)obliqueness { - [self as_setObliqueness:obliqueness range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_expansion:(NSNumber *)expansion { - [self as_setExpansion:expansion range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_baselineOffset:(NSNumber *)baselineOffset { - [self as_setBaselineOffset:baselineOffset range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_verticalGlyphForm:(BOOL)verticalGlyphForm { - [self as_setVerticalGlyphForm:verticalGlyphForm range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_language:(NSString *)language { - [self as_setLanguage:language range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_writingDirection:(NSArray *)writingDirection { - [self as_setWritingDirection:writingDirection range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_paragraphStyle:(NSParagraphStyle *)paragraphStyle { - /* - NSParagraphStyle is NOT toll-free bridged to CTParagraphStyleRef. - - CoreText can use both NSParagraphStyle and CTParagraphStyleRef, - but UILabel/UITextView can only use NSParagraphStyle. - - We use NSParagraphStyle in both CoreText and UIKit. - */ - [self as_setParagraphStyle:paragraphStyle range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_alignment:(NSTextAlignment)alignment { - [self as_setAlignment:alignment range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_baseWritingDirection:(NSWritingDirection)baseWritingDirection { - [self as_setBaseWritingDirection:baseWritingDirection range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_lineSpacing:(CGFloat)lineSpacing { - [self as_setLineSpacing:lineSpacing range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_paragraphSpacing:(CGFloat)paragraphSpacing { - [self as_setParagraphSpacing:paragraphSpacing range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_paragraphSpacingBefore:(CGFloat)paragraphSpacingBefore { - [self as_setParagraphSpacing:paragraphSpacingBefore range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_firstLineHeadIndent:(CGFloat)firstLineHeadIndent { - [self as_setFirstLineHeadIndent:firstLineHeadIndent range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_headIndent:(CGFloat)headIndent { - [self as_setHeadIndent:headIndent range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_tailIndent:(CGFloat)tailIndent { - [self as_setTailIndent:tailIndent range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_lineBreakMode:(NSLineBreakMode)lineBreakMode { - [self as_setLineBreakMode:lineBreakMode range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_minimumLineHeight:(CGFloat)minimumLineHeight { - [self as_setMinimumLineHeight:minimumLineHeight range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_maximumLineHeight:(CGFloat)maximumLineHeight { - [self as_setMaximumLineHeight:maximumLineHeight range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_lineHeightMultiple:(CGFloat)lineHeightMultiple { - [self as_setLineHeightMultiple:lineHeightMultiple range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_hyphenationFactor:(float)hyphenationFactor { - [self as_setHyphenationFactor:hyphenationFactor range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_defaultTabInterval:(CGFloat)defaultTabInterval { - [self as_setDefaultTabInterval:defaultTabInterval range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_tabStops:(NSArray *)tabStops { - [self as_setTabStops:tabStops range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_textShadow:(ASTextShadow *)textShadow { - [self as_setTextShadow:textShadow range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_textInnerShadow:(ASTextShadow *)textInnerShadow { - [self as_setTextInnerShadow:textInnerShadow range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_textUnderline:(ASTextDecoration *)textUnderline { - [self as_setTextUnderline:textUnderline range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_textStrikethrough:(ASTextDecoration *)textStrikethrough { - [self as_setTextStrikethrough:textStrikethrough range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_textBorder:(ASTextBorder *)textBorder { - [self as_setTextBorder:textBorder range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_textBackgroundBorder:(ASTextBorder *)textBackgroundBorder { - [self as_setTextBackgroundBorder:textBackgroundBorder range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_textGlyphTransform:(CGAffineTransform)textGlyphTransform { - [self as_setTextGlyphTransform:textGlyphTransform range:NSMakeRange(0, self.length)]; -} - -#pragma mark - Range Setter - -- (void)as_setFont:(UIFont *)font range:(NSRange)range { - [self as_setAttribute:NSFontAttributeName value:font range:range]; -} - -- (void)as_setKern:(NSNumber *)kern range:(NSRange)range { - [self as_setAttribute:NSKernAttributeName value:kern range:range]; -} - -- (void)as_setColor:(UIColor *)color range:(NSRange)range { - [self as_setAttribute:(id)kCTForegroundColorAttributeName value:(id)color.CGColor range:range]; - [self as_setAttribute:NSForegroundColorAttributeName value:color range:range]; -} - -- (void)as_setBackgroundColor:(UIColor *)backgroundColor range:(NSRange)range { - [self as_setAttribute:NSBackgroundColorAttributeName value:backgroundColor range:range]; -} - -- (void)as_setStrokeWidth:(NSNumber *)strokeWidth range:(NSRange)range { - [self as_setAttribute:NSStrokeWidthAttributeName value:strokeWidth range:range]; -} - -- (void)as_setStrokeColor:(UIColor *)strokeColor range:(NSRange)range { - [self as_setAttribute:(id)kCTStrokeColorAttributeName value:(id)strokeColor.CGColor range:range]; - [self as_setAttribute:NSStrokeColorAttributeName value:strokeColor range:range]; -} - -- (void)as_setShadow:(NSShadow *)shadow range:(NSRange)range { - [self as_setAttribute:NSShadowAttributeName value:shadow range:range]; -} - -- (void)as_setStrikethroughStyle:(NSUnderlineStyle)strikethroughStyle range:(NSRange)range { - NSNumber *style = strikethroughStyle == 0 ? nil : @(strikethroughStyle); - [self as_setAttribute:NSStrikethroughStyleAttributeName value:style range:range]; -} - -- (void)as_setStrikethroughColor:(UIColor *)strikethroughColor range:(NSRange)range { - [self as_setAttribute:NSStrikethroughColorAttributeName value:strikethroughColor range:range]; -} - -- (void)as_setUnderlineStyle:(NSUnderlineStyle)underlineStyle range:(NSRange)range { - NSNumber *style = underlineStyle == 0 ? nil : @(underlineStyle); - [self as_setAttribute:NSUnderlineStyleAttributeName value:style range:range]; -} - -- (void)as_setUnderlineColor:(UIColor *)underlineColor range:(NSRange)range { - [self as_setAttribute:(id)kCTUnderlineColorAttributeName value:(id)underlineColor.CGColor range:range]; - [self as_setAttribute:NSUnderlineColorAttributeName value:underlineColor range:range]; -} - -- (void)as_setLigature:(NSNumber *)ligature range:(NSRange)range { - [self as_setAttribute:NSLigatureAttributeName value:ligature range:range]; -} - -- (void)as_setTextEffect:(NSString *)textEffect range:(NSRange)range { - [self as_setAttribute:NSTextEffectAttributeName value:textEffect range:range]; -} - -- (void)as_setObliqueness:(NSNumber *)obliqueness range:(NSRange)range { - [self as_setAttribute:NSObliquenessAttributeName value:obliqueness range:range]; -} - -- (void)as_setExpansion:(NSNumber *)expansion range:(NSRange)range { - [self as_setAttribute:NSExpansionAttributeName value:expansion range:range]; -} - -- (void)as_setBaselineOffset:(NSNumber *)baselineOffset range:(NSRange)range { - [self as_setAttribute:NSBaselineOffsetAttributeName value:baselineOffset range:range]; -} - -- (void)as_setVerticalGlyphForm:(BOOL)verticalGlyphForm range:(NSRange)range { - NSNumber *v = verticalGlyphForm ? @(YES) : nil; - [self as_setAttribute:NSVerticalGlyphFormAttributeName value:v range:range]; -} - -- (void)as_setLanguage:(NSString *)language range:(NSRange)range { - [self as_setAttribute:(id)kCTLanguageAttributeName value:language range:range]; -} - -- (void)as_setWritingDirection:(NSArray *)writingDirection range:(NSRange)range { - [self as_setAttribute:(id)kCTWritingDirectionAttributeName value:writingDirection range:range]; -} - -- (void)as_setParagraphStyle:(NSParagraphStyle *)paragraphStyle range:(NSRange)range { - /* - NSParagraphStyle is NOT toll-free bridged to CTParagraphStyleRef. - - CoreText can use both NSParagraphStyle and CTParagraphStyleRef, - but UILabel/UITextView can only use NSParagraphStyle. - - We use NSParagraphStyle in both CoreText and UIKit. - */ - [self as_setAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range]; -} - -#define ParagraphStyleSet(_attr_) \ -[self enumerateAttribute:NSParagraphStyleAttributeName \ -inRange:range \ -options:kNilOptions \ -usingBlock: ^(NSParagraphStyle *value, NSRange subRange, BOOL *stop) { \ -NSMutableParagraphStyle *style = nil; \ -if (value) { \ -if (CFGetTypeID((__bridge CFTypeRef)(value)) == CTParagraphStyleGetTypeID()) { \ -value = [NSParagraphStyle as_styleWithCTStyle:(__bridge CTParagraphStyleRef)(value)]; \ -} \ -if (value. _attr_ == _attr_) return; \ -if ([value isKindOfClass:[NSMutableParagraphStyle class]]) { \ -style = (id)value; \ -} else { \ -style = value.mutableCopy; \ -} \ -} else { \ -if ([NSParagraphStyle defaultParagraphStyle]. _attr_ == _attr_) return; \ -style = [NSParagraphStyle defaultParagraphStyle].mutableCopy; \ -} \ -style. _attr_ = _attr_; \ -[self as_setParagraphStyle:style range:subRange]; \ -}]; - -- (void)as_setAlignment:(NSTextAlignment)alignment range:(NSRange)range { - ParagraphStyleSet(alignment); -} - -- (void)as_setBaseWritingDirection:(NSWritingDirection)baseWritingDirection range:(NSRange)range { - ParagraphStyleSet(baseWritingDirection); -} - -- (void)as_setLineSpacing:(CGFloat)lineSpacing range:(NSRange)range { - ParagraphStyleSet(lineSpacing); -} - -- (void)as_setParagraphSpacing:(CGFloat)paragraphSpacing range:(NSRange)range { - ParagraphStyleSet(paragraphSpacing); -} - -- (void)as_setParagraphSpacingBefore:(CGFloat)paragraphSpacingBefore range:(NSRange)range { - ParagraphStyleSet(paragraphSpacingBefore); -} - -- (void)as_setFirstLineHeadIndent:(CGFloat)firstLineHeadIndent range:(NSRange)range { - ParagraphStyleSet(firstLineHeadIndent); -} - -- (void)as_setHeadIndent:(CGFloat)headIndent range:(NSRange)range { - ParagraphStyleSet(headIndent); -} - -- (void)as_setTailIndent:(CGFloat)tailIndent range:(NSRange)range { - ParagraphStyleSet(tailIndent); -} - -- (void)as_setLineBreakMode:(NSLineBreakMode)lineBreakMode range:(NSRange)range { - ParagraphStyleSet(lineBreakMode); -} - -- (void)as_setMinimumLineHeight:(CGFloat)minimumLineHeight range:(NSRange)range { - ParagraphStyleSet(minimumLineHeight); -} - -- (void)as_setMaximumLineHeight:(CGFloat)maximumLineHeight range:(NSRange)range { - ParagraphStyleSet(maximumLineHeight); -} - -- (void)as_setLineHeightMultiple:(CGFloat)lineHeightMultiple range:(NSRange)range { - ParagraphStyleSet(lineHeightMultiple); -} - -- (void)as_setHyphenationFactor:(float)hyphenationFactor range:(NSRange)range { - ParagraphStyleSet(hyphenationFactor); -} - -- (void)as_setDefaultTabInterval:(CGFloat)defaultTabInterval range:(NSRange)range { - ParagraphStyleSet(defaultTabInterval); -} - -- (void)as_setTabStops:(NSArray *)tabStops range:(NSRange)range { - ParagraphStyleSet(tabStops); -} - -#undef ParagraphStyleSet - -- (void)as_setSuperscript:(NSNumber *)superscript range:(NSRange)range { - if ([superscript isEqualToNumber:@(0)]) { - superscript = nil; - } - [self as_setAttribute:(id)kCTSuperscriptAttributeName value:superscript range:range]; -} - -- (void)as_setGlyphInfo:(CTGlyphInfoRef)glyphInfo range:(NSRange)range { - [self as_setAttribute:(id)kCTGlyphInfoAttributeName value:(__bridge id)glyphInfo range:range]; -} - -- (void)as_setCharacterShape:(NSNumber *)characterShape range:(NSRange)range { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [self as_setAttribute:(id)kCTCharacterShapeAttributeName value:characterShape range:range]; -#pragma clang diagnostic pop -} - -- (void)as_setRunDelegate:(CTRunDelegateRef)runDelegate range:(NSRange)range { - [self as_setAttribute:(id)kCTRunDelegateAttributeName value:(__bridge id)runDelegate range:range]; -} - -- (void)as_setBaselineClass:(CFStringRef)baselineClass range:(NSRange)range { - [self as_setAttribute:(id)kCTBaselineClassAttributeName value:(__bridge id)baselineClass range:range]; -} - -- (void)as_setBaselineInfo:(CFDictionaryRef)baselineInfo range:(NSRange)range { - [self as_setAttribute:(id)kCTBaselineInfoAttributeName value:(__bridge id)baselineInfo range:range]; -} - -- (void)as_setBaselineReferenceInfo:(CFDictionaryRef)referenceInfo range:(NSRange)range { - [self as_setAttribute:(id)kCTBaselineReferenceInfoAttributeName value:(__bridge id)referenceInfo range:range]; -} - -- (void)as_setRubyAnnotation:(CTRubyAnnotationRef)ruby range:(NSRange)range { - [self as_setAttribute:(id)kCTRubyAnnotationAttributeName value:(__bridge id)ruby range:range]; -} - -- (void)as_setAttachment:(NSTextAttachment *)attachment range:(NSRange)range { - [self as_setAttribute:NSAttachmentAttributeName value:attachment range:range]; -} - -- (void)as_setLink:(id)link range:(NSRange)range { - [self as_setAttribute:NSLinkAttributeName value:link range:range]; -} - -- (void)as_setTextBackedString:(ASTextBackedString *)textBackedString range:(NSRange)range { - [self as_setAttribute:ASTextBackedStringAttributeName value:textBackedString range:range]; -} - -- (void)as_setTextBinding:(ASTextBinding *)textBinding range:(NSRange)range { - [self as_setAttribute:ASTextBindingAttributeName value:textBinding range:range]; -} - -- (void)as_setTextShadow:(ASTextShadow *)textShadow range:(NSRange)range { - [self as_setAttribute:ASTextShadowAttributeName value:textShadow range:range]; -} - -- (void)as_setTextInnerShadow:(ASTextShadow *)textInnerShadow range:(NSRange)range { - [self as_setAttribute:ASTextInnerShadowAttributeName value:textInnerShadow range:range]; -} - -- (void)as_setTextUnderline:(ASTextDecoration *)textUnderline range:(NSRange)range { - [self as_setAttribute:ASTextUnderlineAttributeName value:textUnderline range:range]; -} - -- (void)as_setTextStrikethrough:(ASTextDecoration *)textStrikethrough range:(NSRange)range { - [self as_setAttribute:ASTextStrikethroughAttributeName value:textStrikethrough range:range]; -} - -- (void)as_setTextBorder:(ASTextBorder *)textBorder range:(NSRange)range { - [self as_setAttribute:ASTextBorderAttributeName value:textBorder range:range]; -} - -- (void)as_setTextBackgroundBorder:(ASTextBorder *)textBackgroundBorder range:(NSRange)range { - [self as_setAttribute:ASTextBackgroundBorderAttributeName value:textBackgroundBorder range:range]; -} - -- (void)as_setTextAttachment:(ASTextAttachment *)textAttachment range:(NSRange)range { - [self as_setAttribute:ASTextAttachmentAttributeName value:textAttachment range:range]; -} - -- (void)as_setTextHighlight:(ASTextHighlight *)textHighlight range:(NSRange)range { - [self as_setAttribute:ASTextHighlightAttributeName value:textHighlight range:range]; -} - -- (void)as_setTextBlockBorder:(ASTextBorder *)textBlockBorder range:(NSRange)range { - [self as_setAttribute:ASTextBlockBorderAttributeName value:textBlockBorder range:range]; -} - -- (void)as_setTextGlyphTransform:(CGAffineTransform)textGlyphTransform range:(NSRange)range { - NSValue *value = CGAffineTransformIsIdentity(textGlyphTransform) ? nil : [NSValue valueWithCGAffineTransform:textGlyphTransform]; - [self as_setAttribute:ASTextGlyphTransformAttributeName value:value range:range]; -} - -- (void)as_setTextHighlightRange:(NSRange)range - color:(UIColor *)color - backgroundColor:(UIColor *)backgroundColor - userInfo:(NSDictionary *)userInfo - tapAction:(ASTextAction)tapAction - longPressAction:(ASTextAction)longPressAction { - ASTextHighlight *highlight = [ASTextHighlight highlightWithBackgroundColor:backgroundColor]; - highlight.userInfo = userInfo; - highlight.tapAction = tapAction; - highlight.longPressAction = longPressAction; - if (color) [self as_setColor:color range:range]; - [self as_setTextHighlight:highlight range:range]; -} - -- (void)as_setTextHighlightRange:(NSRange)range - color:(UIColor *)color - backgroundColor:(UIColor *)backgroundColor - tapAction:(ASTextAction)tapAction { - [self as_setTextHighlightRange:range - color:color - backgroundColor:backgroundColor - userInfo:nil - tapAction:tapAction - longPressAction:nil]; -} - -- (void)as_setTextHighlightRange:(NSRange)range - color:(UIColor *)color - backgroundColor:(UIColor *)backgroundColor - userInfo:(NSDictionary *)userInfo { - [self as_setTextHighlightRange:range - color:color - backgroundColor:backgroundColor - userInfo:userInfo - tapAction:nil - longPressAction:nil]; -} - -- (void)as_insertString:(NSString *)string atIndex:(NSUInteger)location { - [self replaceCharactersInRange:NSMakeRange(location, 0) withString:string]; - [self as_removeDiscontinuousAttributesInRange:NSMakeRange(location, string.length)]; -} - -- (void)as_appendString:(NSString *)string { - NSUInteger length = self.length; - [self replaceCharactersInRange:NSMakeRange(length, 0) withString:string]; - [self as_removeDiscontinuousAttributesInRange:NSMakeRange(length, string.length)]; -} - -- (void)as_removeDiscontinuousAttributesInRange:(NSRange)range { - NSArray *keys = [NSMutableAttributedString as_allDiscontinuousAttributeKeys]; - for (NSString *key in keys) { - [self removeAttribute:key range:range]; - } -} - -+ (NSArray *)as_allDiscontinuousAttributeKeys { - static NSArray *keys; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - keys = @[(id)kCTSuperscriptAttributeName, - (id)kCTRunDelegateAttributeName, - ASTextBackedStringAttributeName, - ASTextBindingAttributeName, - ASTextAttachmentAttributeName, - (id)kCTRubyAnnotationAttributeName, - NSAttachmentAttributeName]; - }); - return keys; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Utility/NSParagraphStyle+ASText.h b/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Utility/NSParagraphStyle+ASText.h deleted file mode 100644 index a7c0aec5ff..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Utility/NSParagraphStyle+ASText.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// NSParagraphStyle+ASText.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - Provides extensions for `NSParagraphStyle` to work with CoreText. - */ -@interface NSParagraphStyle (ASText) - -/** - Creates a new NSParagraphStyle object from the CoreText Style. - - @param CTStyle CoreText Paragraph Style. - - @return a new NSParagraphStyle - */ -+ (nullable NSParagraphStyle *)as_styleWithCTStyle:(CTParagraphStyleRef)CTStyle; - -/** - Creates and returns a CoreText Paragraph Style. (need call CFRelease() after used) - */ -- (nullable CTParagraphStyleRef)as_CTStyle CF_RETURNS_RETAINED; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Utility/NSParagraphStyle+ASText.mm b/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Utility/NSParagraphStyle+ASText.mm deleted file mode 100644 index bc19fd234c..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/TextExperiment/Utility/NSParagraphStyle+ASText.mm +++ /dev/null @@ -1,219 +0,0 @@ -// -// NSParagraphStyle+ASText.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - -// Dummy class for category -@interface NSParagraphStyle_ASText : NSObject @end -@implementation NSParagraphStyle_ASText @end - - -@implementation NSParagraphStyle (ASText) - -+ (NSParagraphStyle *)as_styleWithCTStyle:(CTParagraphStyleRef)CTStyle { - if (CTStyle == NULL) return nil; - - NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; - -#if TARGET_OS_IOS -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - CGFloat lineSpacing; - if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierLineSpacing, sizeof(CGFloat), &lineSpacing)) { - style.lineSpacing = lineSpacing; - } -#pragma clang diagnostic pop -#endif - - CGFloat paragraphSpacing; - if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierParagraphSpacing, sizeof(CGFloat), ¶graphSpacing)) { - style.paragraphSpacing = paragraphSpacing; - } - - CTTextAlignment alignment; - if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierAlignment, sizeof(CTTextAlignment), &alignment)) { - style.alignment = NSTextAlignmentFromCTTextAlignment(alignment); - } - - CGFloat firstLineHeadIndent; - if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierFirstLineHeadIndent, sizeof(CGFloat), &firstLineHeadIndent)) { - style.firstLineHeadIndent = firstLineHeadIndent; - } - - CGFloat headIndent; - if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierHeadIndent, sizeof(CGFloat), &headIndent)) { - style.headIndent = headIndent; - } - - CGFloat tailIndent; - if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierTailIndent, sizeof(CGFloat), &tailIndent)) { - style.tailIndent = tailIndent; - } - - CTLineBreakMode lineBreakMode; - if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierLineBreakMode, sizeof(CTLineBreakMode), &lineBreakMode)) { - style.lineBreakMode = (NSLineBreakMode)lineBreakMode; - } - - CGFloat minimumLineHeight; - if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierMinimumLineHeight, sizeof(CGFloat), &minimumLineHeight)) { - style.minimumLineHeight = minimumLineHeight; - } - - CGFloat maximumLineHeight; - if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierMaximumLineHeight, sizeof(CGFloat), &maximumLineHeight)) { - style.maximumLineHeight = maximumLineHeight; - } - - CTWritingDirection baseWritingDirection; - if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierBaseWritingDirection, sizeof(CTWritingDirection), &baseWritingDirection)) { - style.baseWritingDirection = (NSWritingDirection)baseWritingDirection; - } - - CGFloat lineHeightMultiple; - if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierLineHeightMultiple, sizeof(CGFloat), &lineHeightMultiple)) { - style.lineHeightMultiple = lineHeightMultiple; - } - - CGFloat paragraphSpacingBefore; - if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierParagraphSpacingBefore, sizeof(CGFloat), ¶graphSpacingBefore)) { - style.paragraphSpacingBefore = paragraphSpacingBefore; - } - - CFArrayRef tabStops; - if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierTabStops, sizeof(CFArrayRef), &tabStops)) { - NSMutableArray *tabs = [NSMutableArray new]; - [((__bridge NSArray *)(tabStops))enumerateObjectsUsingBlock : ^(id obj, NSUInteger idx, BOOL *stop) { - CTTextTabRef ctTab = (__bridge CTTextTabRef)obj; - - NSTextTab *tab = [[NSTextTab alloc] initWithTextAlignment:NSTextAlignmentFromCTTextAlignment(CTTextTabGetAlignment(ctTab)) location:CTTextTabGetLocation(ctTab) options:(__bridge id)CTTextTabGetOptions(ctTab)]; - [tabs addObject:tab]; - }]; - if (tabs.count) { - style.tabStops = tabs; - } - } - - CGFloat defaultTabInterval; - if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierDefaultTabInterval, sizeof(CGFloat), &defaultTabInterval)) { - style.defaultTabInterval = defaultTabInterval; - } - - return style; -} - -- (CTParagraphStyleRef)as_CTStyle CF_RETURNS_RETAINED { - CTParagraphStyleSetting set[kCTParagraphStyleSpecifierCount] = { }; - int count = 0; - -#if TARGET_OS_IOS -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - CGFloat lineSpacing = self.lineSpacing; - set[count].spec = kCTParagraphStyleSpecifierLineSpacing; - set[count].valueSize = sizeof(CGFloat); - set[count].value = &lineSpacing; - count++; -#pragma clang diagnostic pop -#endif - - CGFloat paragraphSpacing = self.paragraphSpacing; - set[count].spec = kCTParagraphStyleSpecifierParagraphSpacing; - set[count].valueSize = sizeof(CGFloat); - set[count].value = ¶graphSpacing; - count++; - - CTTextAlignment alignment = NSTextAlignmentToCTTextAlignment(self.alignment); - set[count].spec = kCTParagraphStyleSpecifierAlignment; - set[count].valueSize = sizeof(CTTextAlignment); - set[count].value = &alignment; - count++; - - CGFloat firstLineHeadIndent = self.firstLineHeadIndent; - set[count].spec = kCTParagraphStyleSpecifierFirstLineHeadIndent; - set[count].valueSize = sizeof(CGFloat); - set[count].value = &firstLineHeadIndent; - count++; - - CGFloat headIndent = self.headIndent; - set[count].spec = kCTParagraphStyleSpecifierHeadIndent; - set[count].valueSize = sizeof(CGFloat); - set[count].value = &headIndent; - count++; - - CGFloat tailIndent = self.tailIndent; - set[count].spec = kCTParagraphStyleSpecifierTailIndent; - set[count].valueSize = sizeof(CGFloat); - set[count].value = &tailIndent; - count++; - - CTLineBreakMode paraLineBreak = (CTLineBreakMode)self.lineBreakMode; - set[count].spec = kCTParagraphStyleSpecifierLineBreakMode; - set[count].valueSize = sizeof(CTLineBreakMode); - set[count].value = ¶LineBreak; - count++; - - CGFloat minimumLineHeight = self.minimumLineHeight; - set[count].spec = kCTParagraphStyleSpecifierMinimumLineHeight; - set[count].valueSize = sizeof(CGFloat); - set[count].value = &minimumLineHeight; - count++; - - CGFloat maximumLineHeight = self.maximumLineHeight; - set[count].spec = kCTParagraphStyleSpecifierMaximumLineHeight; - set[count].valueSize = sizeof(CGFloat); - set[count].value = &maximumLineHeight; - count++; - - CTWritingDirection paraWritingDirection = (CTWritingDirection)self.baseWritingDirection; - set[count].spec = kCTParagraphStyleSpecifierBaseWritingDirection; - set[count].valueSize = sizeof(CTWritingDirection); - set[count].value = ¶WritingDirection; - count++; - - CGFloat lineHeightMultiple = self.lineHeightMultiple; - set[count].spec = kCTParagraphStyleSpecifierLineHeightMultiple; - set[count].valueSize = sizeof(CGFloat); - set[count].value = &lineHeightMultiple; - count++; - - CGFloat paragraphSpacingBefore = self.paragraphSpacingBefore; - set[count].spec = kCTParagraphStyleSpecifierParagraphSpacingBefore; - set[count].valueSize = sizeof(CGFloat); - set[count].value = ¶graphSpacingBefore; - count++; - - NSMutableArray *tabs = [NSMutableArray array]; - NSInteger numTabs = self.tabStops.count; - if (numTabs) { - [self.tabStops enumerateObjectsUsingBlock: ^(NSTextTab *tab, NSUInteger idx, BOOL *stop) { - CTTextTabRef ctTab = CTTextTabCreate(NSTextAlignmentToCTTextAlignment(tab.alignment), tab.location, (__bridge CFDictionaryRef)tab.options); - [tabs addObject:(__bridge id)ctTab]; - CFRelease(ctTab); - }]; - - CFArrayRef tabStops = (__bridge CFArrayRef)(tabs); - set[count].spec = kCTParagraphStyleSpecifierTabStops; - set[count].valueSize = sizeof(CFArrayRef); - set[count].value = &tabStops; - count++; - } - - CGFloat defaultTabInterval = self.defaultTabInterval; - set[count].spec = kCTParagraphStyleSpecifierDefaultTabInterval; - set[count].valueSize = sizeof(CGFloat); - set[count].value = &defaultTabInterval; - count++; - - CTParagraphStyleRef style = CTParagraphStyleCreate(set, count); - return style; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/Private/_ASCollectionGalleryLayoutInfo.h b/submodules/AsyncDisplayKit/Source/Private/_ASCollectionGalleryLayoutInfo.h deleted file mode 100644 index 6d7c91e5e5..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/_ASCollectionGalleryLayoutInfo.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// _ASCollectionGalleryLayoutInfo.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@interface _ASCollectionGalleryLayoutInfo : NSObject - -// Read-only properties -@property (nonatomic, readonly) CGSize itemSize; -@property (nonatomic, readonly) CGFloat minimumLineSpacing; -@property (nonatomic, readonly) CGFloat minimumInteritemSpacing; -@property (nonatomic, readonly) UIEdgeInsets sectionInset; - -- (instancetype)initWithItemSize:(CGSize)itemSize - minimumLineSpacing:(CGFloat)minimumLineSpacing - minimumInteritemSpacing:(CGFloat)minimumInteritemSpacing - sectionInset:(UIEdgeInsets)sectionInset NS_DESIGNATED_INITIALIZER; - -- (instancetype)init NS_UNAVAILABLE; - -@end diff --git a/submodules/AsyncDisplayKit/Source/Private/_ASCollectionGalleryLayoutInfo.mm b/submodules/AsyncDisplayKit/Source/Private/_ASCollectionGalleryLayoutInfo.mm deleted file mode 100644 index fec0b52872..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/_ASCollectionGalleryLayoutInfo.mm +++ /dev/null @@ -1,68 +0,0 @@ -// -// _ASCollectionGalleryLayoutInfo.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -@implementation _ASCollectionGalleryLayoutInfo - -- (instancetype)initWithItemSize:(CGSize)itemSize - minimumLineSpacing:(CGFloat)minimumLineSpacing - minimumInteritemSpacing:(CGFloat)minimumInteritemSpacing - sectionInset:(UIEdgeInsets)sectionInset -{ - self = [super init]; - if (self) { - _itemSize = itemSize; - _minimumLineSpacing = minimumLineSpacing; - _minimumInteritemSpacing = minimumInteritemSpacing; - _sectionInset = sectionInset; - } - return self; -} - -- (BOOL)isEqualToInfo:(_ASCollectionGalleryLayoutInfo *)info -{ - if (info == nil) { - return NO; - } - - return CGSizeEqualToSize(_itemSize, info.itemSize) - && _minimumLineSpacing == info.minimumLineSpacing - && _minimumInteritemSpacing == info.minimumInteritemSpacing - && UIEdgeInsetsEqualToEdgeInsets(_sectionInset, info.sectionInset); -} - -- (BOOL)isEqual:(id)other -{ - if (self == other) { - return YES; - } - if (! [other isKindOfClass:[_ASCollectionGalleryLayoutInfo class]]) { - return NO; - } - return [self isEqualToInfo:other]; -} - -- (NSUInteger)hash -{ - struct { - CGSize itemSize; - CGFloat minimumLineSpacing; - CGFloat minimumInteritemSpacing; - UIEdgeInsets sectionInset; - } data = { - _itemSize, - _minimumLineSpacing, - _minimumInteritemSpacing, - _sectionInset, - }; - return ASHashBytes(&data, sizeof(data)); -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/Private/_ASCollectionGalleryLayoutItem.h b/submodules/AsyncDisplayKit/Source/Private/_ASCollectionGalleryLayoutItem.h deleted file mode 100644 index 21df3bcdc0..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/_ASCollectionGalleryLayoutItem.h +++ /dev/null @@ -1,38 +0,0 @@ -// -// _ASCollectionGalleryLayoutItem.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import -#import - -@class ASCollectionElement; - -NS_ASSUME_NONNULL_BEGIN - -/** - * A dummy item that represents a collection element to participate in the collection layout calculation process - * without triggering measurement on the actual node of the collection element. - * - * This item always has a fixed size that is the item size passed to it. - */ -AS_SUBCLASSING_RESTRICTED -@interface _ASGalleryLayoutItem : NSObject - -@property (nonatomic, readonly) CGSize itemSize; -@property (nonatomic, weak, readonly) ASCollectionElement *collectionElement; - -- (instancetype)initWithItemSize:(CGSize)itemSize collectionElement:(ASCollectionElement *)collectionElement; -- (instancetype)init __unavailable; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Private/_ASCollectionGalleryLayoutItem.mm b/submodules/AsyncDisplayKit/Source/Private/_ASCollectionGalleryLayoutItem.mm deleted file mode 100644 index 582d7983cc..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/_ASCollectionGalleryLayoutItem.mm +++ /dev/null @@ -1,85 +0,0 @@ -// -// _ASCollectionGalleryLayoutItem.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -#import -#import -#import -#import -#import - -@implementation _ASGalleryLayoutItem { - std::atomic _primitiveTraitCollection; -} - -@synthesize style; - -- (instancetype)initWithItemSize:(CGSize)itemSize collectionElement:(ASCollectionElement *)collectionElement -{ - self = [super init]; - if (self) { - ASDisplayNodeAssert(! CGSizeEqualToSize(CGSizeZero, itemSize), @"Item size should not be zero"); - ASDisplayNodeAssertNotNil(collectionElement, @"Collection element should not be nil"); - _itemSize = itemSize; - _collectionElement = collectionElement; - } - return self; -} - -ASLayoutElementStyleExtensibilityForwarding -ASPrimitiveTraitCollectionDefaults - -- (ASTraitCollection *)asyncTraitCollection -{ - ASDisplayNodeAssertNotSupported(); - return nil; -} - -- (ASLayoutElementType)layoutElementType -{ - return ASLayoutElementTypeLayoutSpec; -} - -- (NSArray> *)sublayoutElements -{ - ASDisplayNodeAssertNotSupported(); - return nil; -} - -- (BOOL)implementsLayoutMethod -{ - return YES; -} - -ASLayoutElementLayoutCalculationDefaults - -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize -{ - ASDisplayNodeAssert(CGSizeEqualToSize(_itemSize, ASSizeRangeClamp(constrainedSize, _itemSize)), - @"Item size %@ can't fit within the bounds of constrained size %@", NSStringFromCGSize(_itemSize), NSStringFromASSizeRange(constrainedSize)); - return [ASLayout layoutWithLayoutElement:self size:_itemSize]; -} - -#pragma mark - ASLayoutElementAsciiArtProtocol - -- (NSString *)asciiArtString -{ - return [ASLayoutSpec asciiArtStringForChildren:@[] parentName:[self asciiArtName]]; -} - -- (NSString *)asciiArtName -{ - return [NSMutableString stringWithCString:object_getClassName(self) encoding:NSASCIIStringEncoding]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Private/_ASHierarchyChangeSet.h b/submodules/AsyncDisplayKit/Source/Private/_ASHierarchyChangeSet.h deleted file mode 100644 index 800be37517..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/_ASHierarchyChangeSet.h +++ /dev/null @@ -1,218 +0,0 @@ -// -// _ASHierarchyChangeSet.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -typedef NSUInteger ASDataControllerAnimationOptions; - -typedef NS_ENUM(NSInteger, _ASHierarchyChangeType) { - /** - * A reload change, as submitted by the user. When a change set is - * completed, these changes are decomposed into delete-insert pairs - * and combined with the original deletes and inserts of the change. - */ - _ASHierarchyChangeTypeReload, - - /** - * A change that was either an original delete, or the first - * part of a decomposed reload. - */ - _ASHierarchyChangeTypeDelete, - - /** - * A change that was submitted by the user as a delete. - */ - _ASHierarchyChangeTypeOriginalDelete, - - /** - * A change that was either an original insert, or the second - * part of a decomposed reload. - */ - _ASHierarchyChangeTypeInsert, - - /** - * A change that was submitted by the user as an insert. - */ - _ASHierarchyChangeTypeOriginalInsert -}; - -/** - * Returns YES if the given change type is either .Insert or .Delete, NO otherwise. - * Other change types – .Reload, .OriginalInsert, .OriginalDelete – are - * intermediary types used while building the change set. All changes will - * be reduced to either .Insert or .Delete when the change is marked completed. - */ -BOOL ASHierarchyChangeTypeIsFinal(_ASHierarchyChangeType changeType); - -NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType); - -@interface _ASHierarchySectionChange : NSObject - -// FIXME: Generalize this to `changeMetadata` dict? -@property (nonatomic, readonly) ASDataControllerAnimationOptions animationOptions; - -@property (nonatomic, readonly) NSIndexSet *indexSet; - -@property (nonatomic, readonly) _ASHierarchyChangeType changeType; - -/** - * If this is a .OriginalInsert or .OriginalDelete change, this returns a copied change - * with type .Insert or .Delete. Calling this on changes of other types is an error. - */ -- (_ASHierarchySectionChange *)changeByFinalizingType; - -@end - -@interface _ASHierarchyItemChange : NSObject - -@property (nonatomic, readonly) ASDataControllerAnimationOptions animationOptions; - -/// Index paths are sorted descending for changeType .Delete, ascending otherwise -@property (nonatomic, readonly) NSArray *indexPaths; - -@property (nonatomic, readonly) _ASHierarchyChangeType changeType; - -+ (NSDictionary *)sectionToIndexSetMapFromChanges:(NSArray<_ASHierarchyItemChange *> *)changes; - -/** - * If this is a .OriginalInsert or .OriginalDelete change, this returns a copied change - * with type .Insert or .Delete. Calling this on changes of other types is an error. - */ -- (_ASHierarchyItemChange *)changeByFinalizingType; - -@end - -@interface _ASHierarchyChangeSet : NSObject - -/// @precondition The change set must be completed. -@property (nonatomic, readonly) NSIndexSet *deletedSections; - -/// @precondition The change set must be completed. -@property (nonatomic, readonly) NSIndexSet *insertedSections; - -@property (nonatomic, readonly) BOOL completed; - -/// Whether or not changes should be animated. -// TODO: if any update in this chagne set is non-animated, the whole update should be non-animated. -@property (nonatomic) BOOL animated; - -@property (nonatomic, readonly) BOOL includesReloadData; - -/// Indicates whether the change set is empty, that is it includes neither reload data nor per item or section changes. -@property (nonatomic, readonly) BOOL isEmpty; - -/// The count of new ASCellNodes that can undergo async layout calculation. May be zero if all UIKit passthrough cells. -@property (nonatomic, assign) NSUInteger countForAsyncLayout; - -/// The top-level activity for this update. -@property (nonatomic, OS_ACTIVITY_NULLABLE) os_activity_t rootActivity; - -/// The activity for submitting this update i.e. between -beginUpdates and -endUpdates. -@property (nonatomic, OS_ACTIVITY_NULLABLE) os_activity_t submitActivity; - -- (instancetype)initWithOldData:(std::vector)oldItemCounts NS_DESIGNATED_INITIALIZER; - -/** - * Append the given completion handler to the combined @c completionHandler. - * - * @discussion Since batch updates can be nested, we have to support multiple - * completion handlers per update. - * - * @precondition The change set must not be completed. - */ -- (void)addCompletionHandler:(nullable void(^)(BOOL finished))completion; - -/** - * Execute the combined completion handler. - * - * @warning The completion block is discarded after reading because it may have captured - * significant resources that we would like to reclaim as soon as possible. - */ -- (void)executeCompletionHandlerWithFinished:(BOOL)finished; - -/** - * Get the section index after the update for the given section before the update. - * - * @precondition The change set must be completed. - * @return The new section index, or NSNotFound if the given section was deleted. - */ -- (NSUInteger)newSectionForOldSection:(NSUInteger)oldSection; - -/** - * A table that maps old section indexes to new section indexes. - */ -@property (nonatomic, readonly) ASIntegerMap *sectionMapping; - -/** - * A table that maps new section indexes to old section indexes. - */ -@property (nonatomic, readonly) ASIntegerMap *reverseSectionMapping; - -/** - * A table that provides the item mapping for the old section. If the section was deleted - * or is out of bounds, returns the empty table. - */ -- (ASIntegerMap *)itemMappingInSection:(NSInteger)oldSection; - -/** - * A table that provides the reverse item mapping for the new section. If the section was inserted - * or is out of bounds, returns the empty table. - */ -- (ASIntegerMap *)reverseItemMappingInSection:(NSInteger)newSection; - -/** - * Get the old item index path for the given new index path. - * - * @precondition The change set must be completed. - * @return The old index path, or nil if the given item was inserted. - */ -- (nullable NSIndexPath *)oldIndexPathForNewIndexPath:(NSIndexPath *)indexPath; - -/** - * Get the new item index path for the given old index path. - * - * @precondition The change set must be completed. - * @return The new index path, or nil if the given item was deleted. - */ -- (nullable NSIndexPath *)newIndexPathForOldIndexPath:(NSIndexPath *)indexPath; - -/// Call this once the change set has been constructed to prevent future modifications to the changeset. Calling this more than once is a programmer error. -/// NOTE: Calling this method will cause the changeset to convert all reloads into delete/insert pairs. -- (void)markCompletedWithNewItemCounts:(std::vector)newItemCounts; - -- (nullable NSArray <_ASHierarchySectionChange *> *)sectionChangesOfType:(_ASHierarchyChangeType)changeType; - -- (nullable NSArray <_ASHierarchyItemChange *> *)itemChangesOfType:(_ASHierarchyChangeType)changeType; - -/// Returns all item indexes affected by changes of the given type in the given section. -- (NSIndexSet *)indexesForItemChangesOfType:(_ASHierarchyChangeType)changeType inSection:(NSUInteger)section; - -- (void)reloadData; -- (void)deleteSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options; -- (void)insertSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options; -- (void)reloadSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options; -- (void)insertItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options; -- (void)deleteItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options; -- (void)reloadItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options; -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection animationOptions:(ASDataControllerAnimationOptions)options; -- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath animationOptions:(ASDataControllerAnimationOptions)options; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Private/_ASHierarchyChangeSet.mm b/submodules/AsyncDisplayKit/Source/Private/_ASHierarchyChangeSet.mm deleted file mode 100644 index 77d4d1af3f..0000000000 --- a/submodules/AsyncDisplayKit/Source/Private/_ASHierarchyChangeSet.mm +++ /dev/null @@ -1,1016 +0,0 @@ -// -// _ASHierarchyChangeSet.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -// If assertions are enabled and they haven't forced us to suppress the exception, -// then throw, otherwise log. -#if ASDISPLAYNODE_ASSERTIONS_ENABLED - #define ASFailUpdateValidation(...)\ - _Pragma("clang diagnostic push")\ - _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"")\ - if ([ASDisplayNode suppressesInvalidCollectionUpdateExceptions]) {\ - NSLog(__VA_ARGS__);\ - } else {\ - NSLog(__VA_ARGS__);\ - [NSException raise:ASCollectionInvalidUpdateException format:__VA_ARGS__];\ - }\ - _Pragma("clang diagnostic pop") -#else - #define ASFailUpdateValidation(...) NSLog(__VA_ARGS__); -#endif - -BOOL ASHierarchyChangeTypeIsFinal(_ASHierarchyChangeType changeType) { - switch (changeType) { - case _ASHierarchyChangeTypeInsert: - case _ASHierarchyChangeTypeDelete: - return YES; - default: - return NO; - } -} - -NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType) -{ - switch (changeType) { - case _ASHierarchyChangeTypeInsert: - return @"Insert"; - case _ASHierarchyChangeTypeOriginalInsert: - return @"OriginalInsert"; - case _ASHierarchyChangeTypeDelete: - return @"Delete"; - case _ASHierarchyChangeTypeOriginalDelete: - return @"OriginalDelete"; - case _ASHierarchyChangeTypeReload: - return @"Reload"; - default: - return @"(invalid)"; - } -} - -@interface _ASHierarchySectionChange () -- (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexSet:(NSIndexSet *)indexSet animationOptions:(ASDataControllerAnimationOptions)animationOptions; - -/** - On return `changes` is sorted according to the change type with changes coalesced by animationOptions - Assumes: `changes` all have the same changeType - */ -+ (void)sortAndCoalesceSectionChanges:(NSMutableArray<_ASHierarchySectionChange *> *)changes; - -/// Returns all the indexes from all the `indexSet`s of the given `_ASHierarchySectionChange` objects. -+ (NSMutableIndexSet *)allIndexesInSectionChanges:(NSArray *)changes; - -+ (NSString *)smallDescriptionForSectionChanges:(NSArray<_ASHierarchySectionChange *> *)changes; -@end - -@interface _ASHierarchyItemChange () -- (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexPaths:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)animationOptions presorted:(BOOL)presorted; - -/** - On return `changes` is sorted according to the change type with changes coalesced by animationOptions - Assumes: `changes` all have the same changeType - */ -+ (void)sortAndCoalesceItemChanges:(NSMutableArray<_ASHierarchyItemChange *> *)changes ignoringChangesInSections:(NSIndexSet *)sections; - -+ (NSString *)smallDescriptionForItemChanges:(NSArray<_ASHierarchyItemChange *> *)changes; - -+ (void)ensureItemChanges:(NSArray<_ASHierarchyItemChange *> *)changes ofSameType:(_ASHierarchyChangeType)changeType; -@end - -@interface _ASHierarchyChangeSet () - -// array index is old section index, map goes oldItem -> newItem -@property (nonatomic, readonly) NSMutableArray *itemMappings; - -// array index is new section index, map goes newItem -> oldItem -@property (nonatomic, readonly) NSMutableArray *reverseItemMappings; - -@property (nonatomic, readonly) NSMutableArray<_ASHierarchyItemChange *> *insertItemChanges; -@property (nonatomic, readonly) NSMutableArray<_ASHierarchyItemChange *> *originalInsertItemChanges; - -@property (nonatomic, readonly) NSMutableArray<_ASHierarchyItemChange *> *deleteItemChanges; -@property (nonatomic, readonly) NSMutableArray<_ASHierarchyItemChange *> *originalDeleteItemChanges; - -@property (nonatomic, readonly) NSMutableArray<_ASHierarchyItemChange *> *reloadItemChanges; - -@property (nonatomic, readonly) NSMutableArray<_ASHierarchySectionChange *> *insertSectionChanges; -@property (nonatomic, readonly) NSMutableArray<_ASHierarchySectionChange *> *originalInsertSectionChanges; - -@property (nonatomic, readonly) NSMutableArray<_ASHierarchySectionChange *> *deleteSectionChanges; -@property (nonatomic, readonly) NSMutableArray<_ASHierarchySectionChange *> *originalDeleteSectionChanges; - -@property (nonatomic, readonly) NSMutableArray<_ASHierarchySectionChange *> *reloadSectionChanges; - -@end - -@implementation _ASHierarchyChangeSet { - NSUInteger _countForAsyncLayout; - std::vector _oldItemCounts; - std::vector _newItemCounts; - void (^_completionHandler)(BOOL finished); -} -@synthesize sectionMapping = _sectionMapping; -@synthesize reverseSectionMapping = _reverseSectionMapping; -@synthesize itemMappings = _itemMappings; -@synthesize reverseItemMappings = _reverseItemMappings; -@synthesize countForAsyncLayout = _countForAsyncLayout; - -- (instancetype)init -{ - ASFailUpdateValidation(@"_ASHierarchyChangeSet: -init is not supported. Call -initWithOldData:"); - return [self initWithOldData:std::vector()]; -} - -- (instancetype)initWithOldData:(std::vector)oldItemCounts -{ - self = [super init]; - if (self) { - _oldItemCounts = oldItemCounts; - - _originalInsertItemChanges = [[NSMutableArray alloc] init]; - _insertItemChanges = [[NSMutableArray alloc] init]; - _originalDeleteItemChanges = [[NSMutableArray alloc] init]; - _deleteItemChanges = [[NSMutableArray alloc] init]; - _reloadItemChanges = [[NSMutableArray alloc] init]; - - _originalInsertSectionChanges = [[NSMutableArray alloc] init]; - _insertSectionChanges = [[NSMutableArray alloc] init]; - _originalDeleteSectionChanges = [[NSMutableArray alloc] init]; - _deleteSectionChanges = [[NSMutableArray alloc] init]; - _reloadSectionChanges = [[NSMutableArray alloc] init]; - } - return self; -} - -#pragma mark External API - -- (BOOL)isEmpty -{ - return (! _includesReloadData) && (! [self _includesPerItemOrSectionChanges]); -} - -- (void)addCompletionHandler:(void (^)(BOOL))completion -{ - [self _ensureNotCompleted]; - if (completion == nil) { - return; - } - - void (^oldCompletionHandler)(BOOL finished) = _completionHandler; - _completionHandler = ^(BOOL finished) { - if (oldCompletionHandler != nil) { - oldCompletionHandler(finished); - } - completion(finished); - }; -} - -- (void)executeCompletionHandlerWithFinished:(BOOL)finished -{ - if (_completionHandler != nil) { - _completionHandler(finished); - _completionHandler = nil; - } -} - -- (void)markCompletedWithNewItemCounts:(std::vector)newItemCounts -{ - NSAssert(!_completed, @"Attempt to mark already-completed changeset as completed."); - _completed = YES; - _newItemCounts = newItemCounts; - [self _sortAndCoalesceChangeArrays]; - [self _validateUpdate]; -} - -- (NSArray *)sectionChangesOfType:(_ASHierarchyChangeType)changeType -{ - [self _ensureCompleted]; - switch (changeType) { - case _ASHierarchyChangeTypeInsert: - return _insertSectionChanges; - case _ASHierarchyChangeTypeReload: - return _reloadSectionChanges; - case _ASHierarchyChangeTypeDelete: - return _deleteSectionChanges; - case _ASHierarchyChangeTypeOriginalDelete: - return _originalDeleteSectionChanges; - case _ASHierarchyChangeTypeOriginalInsert: - return _originalInsertSectionChanges; - default: - NSAssert(NO, @"Request for section changes with invalid type: %lu", (long)changeType); - return nil; - } -} - -- (NSArray *)itemChangesOfType:(_ASHierarchyChangeType)changeType -{ - [self _ensureCompleted]; - switch (changeType) { - case _ASHierarchyChangeTypeInsert: - return _insertItemChanges; - case _ASHierarchyChangeTypeReload: - return _reloadItemChanges; - case _ASHierarchyChangeTypeDelete: - return _deleteItemChanges; - case _ASHierarchyChangeTypeOriginalInsert: - return _originalInsertItemChanges; - case _ASHierarchyChangeTypeOriginalDelete: - return _originalDeleteItemChanges; - default: - NSAssert(NO, @"Request for item changes with invalid type: %lu", (long)changeType); - return nil; - } -} - -- (NSIndexSet *)indexesForItemChangesOfType:(_ASHierarchyChangeType)changeType inSection:(NSUInteger)section -{ - [self _ensureCompleted]; - const auto result = [[NSMutableIndexSet alloc] init]; - for (_ASHierarchyItemChange *change in [self itemChangesOfType:changeType]) { - [result addIndexes:[NSIndexSet as_indexSetFromIndexPaths:change.indexPaths inSection:section]]; - } - return result; -} - -- (NSUInteger)newSectionForOldSection:(NSUInteger)oldSection -{ - return [self.sectionMapping integerForKey:oldSection]; -} - -- (NSUInteger)oldSectionForNewSection:(NSUInteger)newSection -{ - return [self.reverseSectionMapping integerForKey:newSection]; -} - -- (ASIntegerMap *)sectionMapping -{ - ASDisplayNodeAssertNotNil(_deletedSections, @"Cannot call %s before `markCompleted` returns.", sel_getName(_cmd)); - ASDisplayNodeAssertNotNil(_insertedSections, @"Cannot call %s before `markCompleted` returns.", sel_getName(_cmd)); - [self _ensureCompleted]; - if (_sectionMapping == nil) { - _sectionMapping = [ASIntegerMap mapForUpdateWithOldCount:_oldItemCounts.size() deleted:_deletedSections inserted:_insertedSections]; - } - return _sectionMapping; -} - -- (ASIntegerMap *)reverseSectionMapping -{ - if (_reverseSectionMapping == nil) { - _reverseSectionMapping = [self.sectionMapping inverseMap]; - } - return _reverseSectionMapping; -} - -- (NSMutableArray *)itemMappings -{ - [self _ensureCompleted]; - - if (_itemMappings == nil) { - _itemMappings = [[NSMutableArray alloc] init]; - const auto insertMap = [_ASHierarchyItemChange sectionToIndexSetMapFromChanges:_originalInsertItemChanges]; - const auto deleteMap = [_ASHierarchyItemChange sectionToIndexSetMapFromChanges:_originalDeleteItemChanges]; - NSInteger oldSection = 0; - for (NSInteger oldCount : _oldItemCounts) { - NSInteger newSection = [self newSectionForOldSection:oldSection]; - ASIntegerMap *table; - if (newSection == NSNotFound) { - table = ASIntegerMap.emptyMap; - } else { - table = [ASIntegerMap mapForUpdateWithOldCount:oldCount deleted:deleteMap[@(oldSection)] inserted:insertMap[@(newSection)]]; - } - _itemMappings[oldSection] = table; - oldSection++; - } - } - return _itemMappings; -} - -- (NSMutableArray *)reverseItemMappings -{ - [self _ensureCompleted]; - - if (_reverseItemMappings == nil) { - _reverseItemMappings = [[NSMutableArray alloc] init]; - for (NSInteger newSection = 0; newSection < _newItemCounts.size(); newSection++) { - NSInteger oldSection = [self oldSectionForNewSection:newSection]; - ASIntegerMap *table; - if (oldSection == NSNotFound) { - table = ASIntegerMap.emptyMap; - } else { - table = [[self itemMappingInSection:oldSection] inverseMap]; - } - _reverseItemMappings[newSection] = table; - } - } - return _reverseItemMappings; -} - -- (ASIntegerMap *)itemMappingInSection:(NSInteger)oldSection -{ - if (self.includesReloadData || oldSection >= _oldItemCounts.size()) { - return ASIntegerMap.emptyMap; - } - return self.itemMappings[oldSection]; -} - -- (ASIntegerMap *)reverseItemMappingInSection:(NSInteger)newSection -{ - if (self.includesReloadData || newSection >= _newItemCounts.size()) { - return ASIntegerMap.emptyMap; - } - return self.reverseItemMappings[newSection]; -} - -- (NSIndexPath *)oldIndexPathForNewIndexPath:(NSIndexPath *)indexPath -{ - [self _ensureCompleted]; - NSInteger newSection = indexPath.section; - NSInteger oldSection = [self oldSectionForNewSection:newSection]; - if (oldSection == NSNotFound) { - return nil; - } - NSInteger oldItem = [[self reverseItemMappingInSection:newSection] integerForKey:indexPath.item]; - if (oldItem == NSNotFound) { - return nil; - } - return [NSIndexPath indexPathForItem:oldItem inSection:oldSection]; -} - -- (NSIndexPath *)newIndexPathForOldIndexPath:(NSIndexPath *)indexPath -{ - [self _ensureCompleted]; - NSInteger oldSection = indexPath.section; - NSInteger newSection = [self newSectionForOldSection:oldSection]; - if (newSection == NSNotFound) { - return nil; - } - NSInteger newItem = [[self itemMappingInSection:oldSection] integerForKey:indexPath.item]; - if (newItem == NSNotFound) { - return nil; - } - return [NSIndexPath indexPathForItem:newItem inSection:newSection]; -} - -- (void)reloadData -{ - [self _ensureNotCompleted]; - NSAssert(_includesReloadData == NO, @"Attempt to reload data multiple times %@", self); - _includesReloadData = YES; -} - -- (void)deleteItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options -{ - [self _ensureNotCompleted]; - _ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeOriginalDelete indexPaths:indexPaths animationOptions:options presorted:NO]; - [_originalDeleteItemChanges addObject:change]; -} - -- (void)deleteSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options -{ - [self _ensureNotCompleted]; - _ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeOriginalDelete indexSet:sections animationOptions:options]; - [_originalDeleteSectionChanges addObject:change]; -} - -- (void)insertItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options -{ - [self _ensureNotCompleted]; - _ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeOriginalInsert indexPaths:indexPaths animationOptions:options presorted:NO]; - [_originalInsertItemChanges addObject:change]; -} - -- (void)insertSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options -{ - [self _ensureNotCompleted]; - _ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeOriginalInsert indexSet:sections animationOptions:options]; - [_originalInsertSectionChanges addObject:change]; -} - -- (void)reloadItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options -{ - [self _ensureNotCompleted]; - _ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeReload indexPaths:indexPaths animationOptions:options presorted:NO]; - [_reloadItemChanges addObject:change]; -} - -- (void)reloadSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options -{ - [self _ensureNotCompleted]; - _ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeReload indexSet:sections animationOptions:options]; - [_reloadSectionChanges addObject:change]; -} - -- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath animationOptions:(ASDataControllerAnimationOptions)options -{ - /** - * TODO: Proper move implementation. - */ - [self deleteItems:@[ indexPath ] animationOptions:options]; - [self insertItems:@[ newIndexPath ] animationOptions:options]; -} - -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection animationOptions:(ASDataControllerAnimationOptions)options -{ - /** - * TODO: Proper move implementation. - */ - [self deleteSections:[NSIndexSet indexSetWithIndex:section] animationOptions:options]; - [self insertSections:[NSIndexSet indexSetWithIndex:newSection] animationOptions:options]; -} - -#pragma mark Private - -- (BOOL)_ensureNotCompleted -{ - NSAssert(!_completed, @"Attempt to modify completed changeset %@", self); - return !_completed; -} - -- (BOOL)_ensureCompleted -{ - NSAssert(_completed, @"Attempt to process incomplete changeset %@", self); - return _completed; -} - -- (void)_sortAndCoalesceChangeArrays -{ - if (_includesReloadData) { - return; - } - - @autoreleasepool { - - // Split reloaded sections into [delete(oldIndex), insert(newIndex)] - - // Give these their "pre-reloads" values. Once we add in the reloads we'll re-process them. - _deletedSections = [_ASHierarchySectionChange allIndexesInSectionChanges:_originalDeleteSectionChanges]; - _insertedSections = [_ASHierarchySectionChange allIndexesInSectionChanges:_originalInsertSectionChanges]; - for (_ASHierarchySectionChange *originalDeleteSectionChange in _originalDeleteSectionChanges) { - [_deleteSectionChanges addObject:[originalDeleteSectionChange changeByFinalizingType]]; - } - for (_ASHierarchySectionChange *originalInsertSectionChange in _originalInsertSectionChanges) { - [_insertSectionChanges addObject:[originalInsertSectionChange changeByFinalizingType]]; - } - - for (_ASHierarchySectionChange *change in _reloadSectionChanges) { - NSIndexSet *newSections = [change.indexSet as_indexesByMapping:^(NSUInteger idx) { - NSUInteger newSec = [self newSectionForOldSection:idx]; - ASDisplayNodeAssert(newSec != NSNotFound, @"Request to reload and delete same section %tu", idx); - return newSec; - }]; - - _ASHierarchySectionChange *deleteChange = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeDelete indexSet:change.indexSet animationOptions:change.animationOptions]; - [_deleteSectionChanges addObject:deleteChange]; - - _ASHierarchySectionChange *insertChange = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeInsert indexSet:newSections animationOptions:change.animationOptions]; - [_insertSectionChanges addObject:insertChange]; - } - - [_ASHierarchySectionChange sortAndCoalesceSectionChanges:_deleteSectionChanges]; - [_ASHierarchySectionChange sortAndCoalesceSectionChanges:_insertSectionChanges]; - _deletedSections = [_ASHierarchySectionChange allIndexesInSectionChanges:_deleteSectionChanges]; - _insertedSections = [_ASHierarchySectionChange allIndexesInSectionChanges:_insertSectionChanges]; - - // Split reloaded items into [delete(oldIndexPath), insert(newIndexPath)] - for (_ASHierarchyItemChange *originalDeleteItemChange in _originalDeleteItemChanges) { - [_deleteItemChanges addObject:[originalDeleteItemChange changeByFinalizingType]]; - } - for (_ASHierarchyItemChange *originalInsertItemChange in _originalInsertItemChanges) { - [_insertItemChanges addObject:[originalInsertItemChange changeByFinalizingType]]; - } - - [_ASHierarchyItemChange ensureItemChanges:_insertItemChanges ofSameType:_ASHierarchyChangeTypeInsert]; - [_ASHierarchyItemChange ensureItemChanges:_deleteItemChanges ofSameType:_ASHierarchyChangeTypeDelete]; - - for (_ASHierarchyItemChange *change in _reloadItemChanges) { - NSAssert(change.changeType == _ASHierarchyChangeTypeReload, @"It must be a reload change to be in here"); - - const auto newIndexPaths = ASArrayByFlatMapping(change.indexPaths, NSIndexPath *indexPath, [self newIndexPathForOldIndexPath:indexPath]); - - // All reload changes are translated into deletes and inserts - // We delete the items that needs reload together with other deleted items, at their original index - _ASHierarchyItemChange *deleteItemChangeFromReloadChange = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeDelete indexPaths:change.indexPaths animationOptions:change.animationOptions presorted:NO]; - [_deleteItemChanges addObject:deleteItemChangeFromReloadChange]; - // We insert the items that needs reload together with other inserted items, at their future index - _ASHierarchyItemChange *insertItemChangeFromReloadChange = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeInsert indexPaths:newIndexPaths animationOptions:change.animationOptions presorted:NO]; - [_insertItemChanges addObject:insertItemChangeFromReloadChange]; - } - - // Ignore item deletes in reloaded/deleted sections. - [_ASHierarchyItemChange sortAndCoalesceItemChanges:_deleteItemChanges ignoringChangesInSections:_deletedSections]; - - // Ignore item inserts in reloaded(new)/inserted sections. - [_ASHierarchyItemChange sortAndCoalesceItemChanges:_insertItemChanges ignoringChangesInSections:_insertedSections]; - } -} - -- (void)_validateUpdate -{ - // If reloadData exists, ignore other changes - if (_includesReloadData) { - if ([self _includesPerItemOrSectionChanges]) { - NSLog(@"Warning: A reload data shouldn't be used in conjuntion with other updates."); - } - return; - } - - NSIndexSet *allReloadedSections = [_ASHierarchySectionChange allIndexesInSectionChanges:_reloadSectionChanges]; - - NSInteger newSectionCount = _newItemCounts.size(); - NSInteger oldSectionCount = _oldItemCounts.size(); - - NSInteger insertedSectionCount = _insertedSections.count; - NSInteger deletedSectionCount = _deletedSections.count; - // Assert that the new section count is correct. - if (newSectionCount != oldSectionCount + insertedSectionCount - deletedSectionCount) { - ASFailUpdateValidation(@"Invalid number of sections. The number of sections after the update (%ld) must be equal to the number of sections before the update (%ld) plus or minus the number of sections inserted or deleted (%ld inserted, %ld deleted)", (long)newSectionCount, (long)oldSectionCount, (long)insertedSectionCount, (long)deletedSectionCount); - return; - } - - // Assert that no invalid deletes/reloads happened. - NSInteger invalidSectionDelete = NSNotFound; - if (oldSectionCount == 0) { - invalidSectionDelete = _deletedSections.firstIndex; - } else { - invalidSectionDelete = [_deletedSections indexGreaterThanIndex:oldSectionCount - 1]; - } - if (invalidSectionDelete != NSNotFound) { - ASFailUpdateValidation(@"Attempt to delete section %ld but there are only %ld sections before the update.", (long)invalidSectionDelete, (long)oldSectionCount); - return; - } - - for (_ASHierarchyItemChange *change in _deleteItemChanges) { - for (NSIndexPath *indexPath in change.indexPaths) { - // Assert that item delete happened in a valid section. - NSInteger section = indexPath.section; - NSInteger item = indexPath.item; - if (section >= oldSectionCount) { - ASFailUpdateValidation(@"Attempt to delete item %ld from section %ld, but there are only %ld sections before the update.", (long)item, (long)section, (long)oldSectionCount); - return; - } - - // Assert that item delete happened to a valid item. - NSInteger oldItemCount = _oldItemCounts[section]; - if (item >= oldItemCount) { - ASFailUpdateValidation(@"Attempt to delete item %ld from section %ld, which only contains %ld items before the update.", (long)item, (long)section, (long)oldItemCount); - return; - } - } - } - - for (_ASHierarchyItemChange *change in _insertItemChanges) { - for (NSIndexPath *indexPath in change.indexPaths) { - NSInteger section = indexPath.section; - NSInteger item = indexPath.item; - // Assert that item insert happened in a valid section. - if (section >= newSectionCount) { - ASFailUpdateValidation(@"Attempt to insert item %ld into section %ld, but there are only %ld sections after the update.", (long)item, (long)section, (long)newSectionCount); - return; - } - - // Assert that item delete happened to a valid item. - NSInteger newItemCount = _newItemCounts[section]; - if (item >= newItemCount) { - ASFailUpdateValidation(@"Attempt to insert item %ld into section %ld, which only contains %ld items after the update.", (long)item, (long)section, (long)newItemCount); - return; - } - } - } - - // Assert that no sections were inserted out of bounds. - NSInteger invalidSectionInsert = NSNotFound; - if (newSectionCount == 0) { - invalidSectionInsert = _insertedSections.firstIndex; - } else { - invalidSectionInsert = [_insertedSections indexGreaterThanIndex:newSectionCount - 1]; - } - if (invalidSectionInsert != NSNotFound) { - ASFailUpdateValidation(@"Attempt to insert section %ld but there are only %ld sections after the update.", (long)invalidSectionInsert, (long)newSectionCount); - return; - } - - for (NSUInteger oldSection = 0; oldSection < oldSectionCount; oldSection++) { - NSInteger oldItemCount = _oldItemCounts[oldSection]; - // If section was reloaded, ignore. - if ([allReloadedSections containsIndex:oldSection]) { - continue; - } - - // If section was deleted, ignore. - NSUInteger newSection = [self newSectionForOldSection:oldSection]; - if (newSection == NSNotFound) { - continue; - } - - NSIndexSet *originalInsertedItems = [self indexesForItemChangesOfType:_ASHierarchyChangeTypeOriginalInsert inSection:newSection]; - NSIndexSet *originalDeletedItems = [self indexesForItemChangesOfType:_ASHierarchyChangeTypeOriginalDelete inSection:oldSection]; - NSIndexSet *reloadedItems = [self indexesForItemChangesOfType:_ASHierarchyChangeTypeReload inSection:oldSection]; - - // Assert that no reloaded items were deleted. - NSInteger deletedReloadedItem = [originalDeletedItems as_intersectionWithIndexes:reloadedItems].firstIndex; - if (deletedReloadedItem != NSNotFound) { - ASFailUpdateValidation(@"Attempt to delete and reload the same item at index path %@", [NSIndexPath indexPathForItem:deletedReloadedItem inSection:oldSection]); - return; - } - - // Assert that the new item count is correct. - NSInteger newItemCount = _newItemCounts[newSection]; - NSInteger insertedItemCount = originalInsertedItems.count; - NSInteger deletedItemCount = originalDeletedItems.count; - if (newItemCount != oldItemCount + insertedItemCount - deletedItemCount) { - ASFailUpdateValidation(@"Invalid number of items in section %ld. The number of items after the update (%ld) must be equal to the number of items before the update (%ld) plus or minus the number of items inserted or deleted (%ld inserted, %ld deleted).", (long)oldSection, (long)newItemCount, (long)oldItemCount, (long)insertedItemCount, (long)deletedItemCount); - return; - } - } -} - -- (BOOL)_includesPerItemOrSectionChanges -{ - return 0 < (_originalDeleteSectionChanges.count + _originalDeleteItemChanges.count - +_originalInsertSectionChanges.count + _originalInsertItemChanges.count - + _reloadSectionChanges.count + _reloadItemChanges.count); -} - -#pragma mark - Debugging (Private) - -- (NSString *)description -{ - return ASObjectDescriptionMakeWithoutObject([self propertiesForDescription]); -} - -- (NSString *)debugDescription -{ - return ASObjectDescriptionMake(self, [self propertiesForDebugDescription]); -} - -- (NSMutableArray *)propertiesForDescription -{ - NSMutableArray *result = [NSMutableArray array]; - if (_includesReloadData) { - [result addObject:@{ @"reloadData" : @"YES" }]; - } - if (_reloadSectionChanges.count > 0) { - [result addObject:@{ @"reloadSections" : [_ASHierarchySectionChange smallDescriptionForSectionChanges:_reloadSectionChanges] }]; - } - if (_reloadItemChanges.count > 0) { - [result addObject:@{ @"reloadItems" : [_ASHierarchyItemChange smallDescriptionForItemChanges:_reloadItemChanges] }]; - } - if (_originalDeleteSectionChanges.count > 0) { - [result addObject:@{ @"deleteSections" : [_ASHierarchySectionChange smallDescriptionForSectionChanges:_originalDeleteSectionChanges] }]; - } - if (_originalDeleteItemChanges.count > 0) { - [result addObject:@{ @"deleteItems" : [_ASHierarchyItemChange smallDescriptionForItemChanges:_originalDeleteItemChanges] }]; - } - if (_originalInsertSectionChanges.count > 0) { - [result addObject:@{ @"insertSections" : [_ASHierarchySectionChange smallDescriptionForSectionChanges:_originalInsertSectionChanges] }]; - } - if (_originalInsertItemChanges.count > 0) { - [result addObject:@{ @"insertItems" : [_ASHierarchyItemChange smallDescriptionForItemChanges:_originalInsertItemChanges] }]; - } - return result; -} - -- (NSMutableArray *)propertiesForDebugDescription -{ - return [self propertiesForDescription]; -} - -@end - -@implementation _ASHierarchySectionChange - -- (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexSet:(NSIndexSet *)indexSet animationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - self = [super init]; - if (self) { - ASDisplayNodeAssert(indexSet.count > 0, @"Request to create _ASHierarchySectionChange with no sections!"); - _changeType = changeType; - _indexSet = indexSet; - _animationOptions = animationOptions; - } - return self; -} - -- (_ASHierarchySectionChange *)changeByFinalizingType -{ - _ASHierarchyChangeType newType; - switch (_changeType) { - case _ASHierarchyChangeTypeOriginalInsert: - newType = _ASHierarchyChangeTypeInsert; - break; - case _ASHierarchyChangeTypeOriginalDelete: - newType = _ASHierarchyChangeTypeDelete; - break; - default: - ASFailUpdateValidation(@"Attempt to finalize section change of invalid type %@.", NSStringFromASHierarchyChangeType(_changeType)); - return self; - } - return [[_ASHierarchySectionChange alloc] initWithChangeType:newType indexSet:_indexSet animationOptions:_animationOptions]; -} - -+ (void)sortAndCoalesceSectionChanges:(NSMutableArray<_ASHierarchySectionChange *> *)changes -{ - _ASHierarchySectionChange *firstChange = changes.firstObject; - if (firstChange == nil) { - return; - } - _ASHierarchyChangeType type = [firstChange changeType]; - - ASDisplayNodeAssert(ASHierarchyChangeTypeIsFinal(type), @"Attempt to sort and coalesce section changes of intermediary type %@. Why?", NSStringFromASHierarchyChangeType(type)); - - // Lookup table [Int: AnimationOptions] - __block std::unordered_map animationOptions; - - // All changed indexes - NSMutableIndexSet *allIndexes = [NSMutableIndexSet new]; - - for (_ASHierarchySectionChange *change in changes) { - ASDataControllerAnimationOptions options = change.animationOptions; - NSIndexSet *indexes = change.indexSet; - [indexes enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { - for (NSUInteger i = range.location; i < NSMaxRange(range); i++) { - animationOptions[i] = options; - } - }]; - [allIndexes addIndexes:indexes]; - } - - // Create new changes by grouping sorted changes by animation option - NSMutableArray *result = [[NSMutableArray alloc] init]; - - __block ASDataControllerAnimationOptions currentOptions = 0; - const auto currentIndexes = [[NSMutableIndexSet alloc] init]; - - BOOL reverse = type == _ASHierarchyChangeTypeDelete || type == _ASHierarchyChangeTypeOriginalDelete; - NSEnumerationOptions options = reverse ? NSEnumerationReverse : kNilOptions; - - [allIndexes enumerateRangesWithOptions:options usingBlock:^(NSRange range, BOOL * _Nonnull stop) { - NSInteger increment = reverse ? -1 : 1; - NSUInteger start = reverse ? NSMaxRange(range) - 1 : range.location; - NSInteger limit = reverse ? range.location - 1 : NSMaxRange(range); - for (NSInteger i = start; i != limit; i += increment) { - ASDataControllerAnimationOptions options = animationOptions[i]; - - // End the previous group if needed. - if (options != currentOptions && currentIndexes.count > 0) { - _ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:type indexSet:[currentIndexes copy] animationOptions:currentOptions]; - [result addObject:change]; - [currentIndexes removeAllIndexes]; - } - - // Start a new group if needed. - if (currentIndexes.count == 0) { - currentOptions = options; - } - - [currentIndexes addIndex:i]; - } - }]; - - // Finish up the last group. - if (currentIndexes.count > 0) { - _ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:type indexSet:[currentIndexes copy] animationOptions:currentOptions]; - [result addObject:change]; - } - - [changes setArray:result]; -} - -+ (NSMutableIndexSet *)allIndexesInSectionChanges:(NSArray<_ASHierarchySectionChange *> *)changes -{ - const auto indexes = [[NSMutableIndexSet alloc] init]; - for (_ASHierarchySectionChange *change in changes) { - [indexes addIndexes:change.indexSet]; - } - return indexes; -} - -#pragma mark - Debugging (Private) - -+ (NSString *)smallDescriptionForSectionChanges:(NSArray<_ASHierarchySectionChange *> *)changes -{ - NSMutableIndexSet *unionIndexSet = [NSMutableIndexSet indexSet]; - for (_ASHierarchySectionChange *change in changes) { - [unionIndexSet addIndexes:change.indexSet]; - } - return [unionIndexSet as_smallDescription]; -} - -- (NSString *)description -{ - return ASObjectDescriptionMake(self, [self propertiesForDescription]); -} - -- (NSString *)debugDescription -{ - return ASObjectDescriptionMake(self, [self propertiesForDebugDescription]); -} - -- (NSString *)smallDescription -{ - return [self.indexSet as_smallDescription]; -} - -- (NSMutableArray *)propertiesForDescription -{ - NSMutableArray *result = [NSMutableArray array]; - [result addObject:@{ @"indexes" : [self.indexSet as_smallDescription] }]; - return result; -} - -- (NSMutableArray *)propertiesForDebugDescription -{ - NSMutableArray *result = [NSMutableArray array]; - [result addObject:@{ @"anim" : @(_animationOptions) }]; - [result addObject:@{ @"type" : NSStringFromASHierarchyChangeType(_changeType) }]; - [result addObject:@{ @"indexes" : self.indexSet }]; - return result; -} - -@end - -@implementation _ASHierarchyItemChange - -- (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexPaths:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)animationOptions presorted:(BOOL)presorted -{ - self = [super init]; - if (self) { - ASDisplayNodeAssert(indexPaths.count > 0, @"Request to create _ASHierarchyItemChange with no items!"); - _changeType = changeType; - if (presorted) { - _indexPaths = indexPaths; - } else { - SEL sorting = changeType == _ASHierarchyChangeTypeDelete ? @selector(asdk_inverseCompare:) : @selector(compare:); - _indexPaths = [indexPaths sortedArrayUsingSelector:sorting]; - } - _animationOptions = animationOptions; - } - return self; -} - -// Create a mapping out of changes indexPaths to a {@section : [indexSet]} fashion -// e.g. changes: (0 - 0), (0 - 1), (2 - 5) -// will become: {@0 : [0, 1], @2 : [5]} -+ (NSDictionary *)sectionToIndexSetMapFromChanges:(NSArray<_ASHierarchyItemChange *> *)changes -{ - NSMutableDictionary *sectionToIndexSetMap = [[NSMutableDictionary alloc] init]; - for (_ASHierarchyItemChange *change in changes) { - for (NSIndexPath *indexPath in change.indexPaths) { - NSNumber *sectionKey = @(indexPath.section); - NSMutableIndexSet *indexSet = sectionToIndexSetMap[sectionKey]; - if (indexSet) { - [indexSet addIndex:indexPath.item]; - } else { - indexSet = [NSMutableIndexSet indexSetWithIndex:indexPath.item]; - sectionToIndexSetMap[sectionKey] = indexSet; - } - } - } - return sectionToIndexSetMap; -} - -+ (void)ensureItemChanges:(NSArray<_ASHierarchyItemChange *> *)changes ofSameType:(_ASHierarchyChangeType)changeType -{ -#if ASDISPLAYNODE_ASSERTIONS_ENABLED - for (_ASHierarchyItemChange *change in changes) { - NSAssert(change.changeType == changeType, @"The map we created must all be of the same changeType as of now"); - } -#endif -} - -- (_ASHierarchyItemChange *)changeByFinalizingType -{ - _ASHierarchyChangeType newType; - switch (_changeType) { - case _ASHierarchyChangeTypeOriginalInsert: - newType = _ASHierarchyChangeTypeInsert; - break; - case _ASHierarchyChangeTypeOriginalDelete: - newType = _ASHierarchyChangeTypeDelete; - break; - default: - ASFailUpdateValidation(@"Attempt to finalize item change of invalid type %@.", NSStringFromASHierarchyChangeType(_changeType)); - return self; - } - return [[_ASHierarchyItemChange alloc] initWithChangeType:newType indexPaths:_indexPaths animationOptions:_animationOptions presorted:YES]; -} - -+ (void)sortAndCoalesceItemChanges:(NSMutableArray<_ASHierarchyItemChange *> *)changes ignoringChangesInSections:(NSIndexSet *)ignoredSections -{ - if (changes.count < 1) { - return; - } - - _ASHierarchyChangeType type = [changes.firstObject changeType]; - ASDisplayNodeAssert(ASHierarchyChangeTypeIsFinal(type), @"Attempt to sort and coalesce item changes of intermediary type %@. Why?", NSStringFromASHierarchyChangeType(type)); - - // Lookup table [NSIndexPath: AnimationOptions] - const auto animationOptions = [[NSMutableDictionary alloc] init]; - - // All changed index paths, sorted - const auto allIndexPaths = [[NSMutableArray alloc] init]; - - for (_ASHierarchyItemChange *change in changes) { - for (NSIndexPath *indexPath in change.indexPaths) { - if (![ignoredSections containsIndex:indexPath.section]) { - animationOptions[indexPath] = @(change.animationOptions); - [allIndexPaths addObject:indexPath]; - } - } - } - - SEL sorting = type == _ASHierarchyChangeTypeDelete ? @selector(asdk_inverseCompare:) : @selector(compare:); - [allIndexPaths sortUsingSelector:sorting]; - - // Create new changes by grouping sorted changes by animation option - const auto result = [[NSMutableArray<_ASHierarchyItemChange *> alloc] init]; - - ASDataControllerAnimationOptions currentOptions = 0; - const auto currentIndexPaths = [[NSMutableArray alloc] init]; - - for (NSIndexPath *indexPath in allIndexPaths) { - ASDataControllerAnimationOptions options = [animationOptions[indexPath] integerValue]; - - // End the previous group if needed. - if (options != currentOptions && currentIndexPaths.count > 0) { - _ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:type indexPaths:[currentIndexPaths copy] animationOptions:currentOptions presorted:YES]; - [result addObject:change]; - [currentIndexPaths removeAllObjects]; - } - - // Start a new group if needed. - if (currentIndexPaths.count == 0) { - currentOptions = options; - } - - [currentIndexPaths addObject:indexPath]; - } - - // Finish up the last group. - if (currentIndexPaths.count > 0) { - _ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:type indexPaths:[currentIndexPaths copy] animationOptions:currentOptions presorted:YES]; - [result addObject:change]; - } - - [changes setArray:result]; -} - -#pragma mark - Debugging (Private) - -+ (NSString *)smallDescriptionForItemChanges:(NSArray<_ASHierarchyItemChange *> *)changes -{ - NSDictionary *map = [self sectionToIndexSetMapFromChanges:changes]; - NSMutableString *str = [NSMutableString stringWithString:@"{ "]; - [map enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull section, NSIndexSet * _Nonnull indexSet, BOOL * _Nonnull stop) { - [str appendFormat:@"@%lu : %@ ", (long)section.integerValue, [indexSet as_smallDescription]]; - }]; - [str appendString:@"}"]; - return str; -} - -- (NSString *)description -{ - return ASObjectDescriptionMake(self, [self propertiesForDescription]); -} - -- (NSString *)debugDescription -{ - return ASObjectDescriptionMake(self, [self propertiesForDebugDescription]); -} - -- (NSMutableArray *)propertiesForDescription -{ - NSMutableArray *result = [NSMutableArray array]; - [result addObject:@{ @"indexPaths" : self.indexPaths }]; - return result; -} - -- (NSMutableArray *)propertiesForDebugDescription -{ - NSMutableArray *result = [NSMutableArray array]; - [result addObject:@{ @"anim" : @(_animationOptions) }]; - [result addObject:@{ @"type" : NSStringFromASHierarchyChangeType(_changeType) }]; - [result addObject:@{ @"indexPaths" : self.indexPaths }]; - return result; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASAbsoluteLayoutElement.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASAbsoluteLayoutElement.h similarity index 96% rename from submodules/AsyncDisplayKit/Source/Layout/ASAbsoluteLayoutElement.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASAbsoluteLayoutElement.h index 109816de20..18f488d9de 100644 --- a/submodules/AsyncDisplayKit/Source/Layout/ASAbsoluteLayoutElement.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASAbsoluteLayoutElement.h @@ -8,6 +8,7 @@ // #import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASAsciiArtBoxCreator.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASAsciiArtBoxCreator.h similarity index 98% rename from submodules/AsyncDisplayKit/Source/Layout/ASAsciiArtBoxCreator.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASAsciiArtBoxCreator.h index 83c7bec414..6d4fe22ea9 100644 --- a/submodules/AsyncDisplayKit/Source/Layout/ASAsciiArtBoxCreator.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASAsciiArtBoxCreator.h @@ -8,6 +8,7 @@ // #import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/submodules/AsyncDisplayKit/Source/Base/ASAssert.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASAssert.h similarity index 99% rename from submodules/AsyncDisplayKit/Source/Base/ASAssert.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASAssert.h index f00ce967d6..d6e9dad1c2 100644 --- a/submodules/AsyncDisplayKit/Source/Base/ASAssert.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASAssert.h @@ -9,6 +9,7 @@ #pragma once +#import #import #import #import diff --git a/submodules/AsyncDisplayKit/Source/Base/ASAvailability.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASAvailability.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/Base/ASAvailability.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASAvailability.h diff --git a/submodules/AsyncDisplayKit/Source/Base/ASBaseDefines.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASBaseDefines.h similarity index 99% rename from submodules/AsyncDisplayKit/Source/Base/ASBaseDefines.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASBaseDefines.h index 4e880f9024..dd15d1aca4 100644 --- a/submodules/AsyncDisplayKit/Source/Base/ASBaseDefines.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASBaseDefines.h @@ -8,6 +8,7 @@ // #import +#import #define AS_EXTERN FOUNDATION_EXTERN #define unowned __unsafe_unretained diff --git a/submodules/AsyncDisplayKit/Source/ASBlockTypes.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASBlockTypes.h similarity index 96% rename from submodules/AsyncDisplayKit/Source/ASBlockTypes.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASBlockTypes.h index e1c7456019..a4c22ff0f4 100644 --- a/submodules/AsyncDisplayKit/Source/ASBlockTypes.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASBlockTypes.h @@ -8,6 +8,7 @@ // #import +#import @class ASCellNode; diff --git a/submodules/AsyncDisplayKit/Source/ASCollections.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASCollections.h similarity index 97% rename from submodules/AsyncDisplayKit/Source/ASCollections.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASCollections.h index ca3b31c65c..ce1f0d1fd2 100644 --- a/submodules/AsyncDisplayKit/Source/ASCollections.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASCollections.h @@ -7,6 +7,7 @@ // #import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/submodules/AsyncDisplayKit/Source/ASConfiguration.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASConfiguration.h similarity index 98% rename from submodules/AsyncDisplayKit/Source/ASConfiguration.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASConfiguration.h index c529dad801..a97f62bb1b 100644 --- a/submodules/AsyncDisplayKit/Source/ASConfiguration.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASConfiguration.h @@ -7,6 +7,7 @@ // #import +#import #import #import diff --git a/submodules/AsyncDisplayKit/Source/ASConfigurationDelegate.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASConfigurationDelegate.h similarity index 97% rename from submodules/AsyncDisplayKit/Source/ASConfigurationDelegate.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASConfigurationDelegate.h index 127ec3eb14..45fdec442c 100644 --- a/submodules/AsyncDisplayKit/Source/ASConfigurationDelegate.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASConfigurationDelegate.h @@ -7,6 +7,7 @@ // #import +#import #import NS_ASSUME_NONNULL_BEGIN diff --git a/submodules/AsyncDisplayKit/Source/ASConfigurationInternal.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASConfigurationInternal.h similarity index 98% rename from submodules/AsyncDisplayKit/Source/ASConfigurationInternal.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASConfigurationInternal.h index eb639c2243..7164d041cb 100644 --- a/submodules/AsyncDisplayKit/Source/ASConfigurationInternal.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASConfigurationInternal.h @@ -10,6 +10,7 @@ /// It will be private again after exp_unfair_lock ends. #import +#import #import NS_ASSUME_NONNULL_BEGIN diff --git a/submodules/AsyncDisplayKit/Source/ASContextTransitioning.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASContextTransitioning.h similarity index 98% rename from submodules/AsyncDisplayKit/Source/ASContextTransitioning.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASContextTransitioning.h index 9802ecefc4..6bd5fb7b5f 100644 --- a/submodules/AsyncDisplayKit/Source/ASContextTransitioning.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASContextTransitioning.h @@ -7,6 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // +#import #import @class ASDisplayNode; diff --git a/submodules/AsyncDisplayKit/Source/ASControlNode+Subclasses.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASControlNode+Subclasses.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASControlNode+Subclasses.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASControlNode+Subclasses.h diff --git a/submodules/AsyncDisplayKit/Source/ASControlNode.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASControlNode.h similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASControlNode.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASControlNode.h index 0918fdb0b0..4370967387 100644 --- a/submodules/AsyncDisplayKit/Source/ASControlNode.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASControlNode.h @@ -7,6 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // +#import #import #pragma once diff --git a/submodules/AsyncDisplayKit/Source/Private/ASControlTargetAction.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASControlTargetAction.h similarity index 97% rename from submodules/AsyncDisplayKit/Source/Private/ASControlTargetAction.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASControlTargetAction.h index 5a3595c5d3..c37e8504bb 100644 --- a/submodules/AsyncDisplayKit/Source/Private/ASControlTargetAction.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASControlTargetAction.h @@ -8,6 +8,7 @@ // #import +#import /** @abstract ASControlTargetAction stores target action pairs registered for specific ASControlNodeEvent values. diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASDimension.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDimension.h similarity index 99% rename from submodules/AsyncDisplayKit/Source/Layout/ASDimension.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDimension.h index 0b3c6b7beb..a10839459c 100644 --- a/submodules/AsyncDisplayKit/Source/Layout/ASDimension.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDimension.h @@ -8,6 +8,8 @@ // #pragma once + +#import #import #import #import diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASDimensionInternal.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDimensionInternal.h similarity index 99% rename from submodules/AsyncDisplayKit/Source/Layout/ASDimensionInternal.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDimensionInternal.h index df8f05600c..7bdcf8b739 100644 --- a/submodules/AsyncDisplayKit/Source/Layout/ASDimensionInternal.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDimensionInternal.h @@ -7,6 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // +#import #import #import diff --git a/submodules/AsyncDisplayKit/Source/Base/ASDisplayNode+Ancestry.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode+Ancestry.h similarity index 98% rename from submodules/AsyncDisplayKit/Source/Base/ASDisplayNode+Ancestry.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode+Ancestry.h index 2c61cee00c..a952e96a34 100644 --- a/submodules/AsyncDisplayKit/Source/Base/ASDisplayNode+Ancestry.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode+Ancestry.h @@ -8,6 +8,7 @@ // #import +#import #import NS_ASSUME_NONNULL_BEGIN diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Beta.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode+Beta.h similarity index 89% rename from submodules/AsyncDisplayKit/Source/ASDisplayNode+Beta.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode+Beta.h index 2bb2a4c932..d08bce5d66 100644 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Beta.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode+Beta.h @@ -7,16 +7,9 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // +#import #import #import -#import -#import - -#if YOGA - #import YOGA_HEADER_PATH - #import - #import -#endif NS_ASSUME_NONNULL_BEGIN @@ -152,15 +145,6 @@ AS_CATEGORY_IMPLEMENTABLE AS_CATEGORY_IMPLEMENTABLE - (void)willCalculateLayout:(ASSizeRange)constrainedSize NS_REQUIRES_SUPER; -/** - * Only ASLayoutRangeModeVisibleOnly or ASLayoutRangeModeLowMemory are recommended. Default is ASLayoutRangeModeVisibleOnly, - * because this is the only way to ensure an application will not have blank / flashing views as the user navigates back after - * a memory warning. Apps that wish to use the more effective / aggressive ASLayoutRangeModeLowMemory may need to take steps - * to mitigate this behavior, including: restoring a larger range mode to the next controller before the user navigates there, - * enabling .neverShowPlaceholders on ASCellNodes so that the navigation operation is blocked on redisplay completing, etc. - */ -+ (void)setRangeModeForMemoryWarnings:(ASLayoutRangeMode)rangeMode; - /** * @abstract Whether to draw all descendent nodes' contents into this node's layer's backing store. * diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Convenience.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode+Convenience.h similarity index 97% rename from submodules/AsyncDisplayKit/Source/ASDisplayNode+Convenience.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode+Convenience.h index 3c00f67213..b7ff13220e 100644 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Convenience.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode+Convenience.h @@ -7,6 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // +#import #import NS_ASSUME_NONNULL_BEGIN diff --git a/submodules/AsyncDisplayKit/Source/Private/ASDisplayNode+FrameworkPrivate.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h similarity index 99% rename from submodules/AsyncDisplayKit/Source/Private/ASDisplayNode+FrameworkPrivate.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h index 0c101697a6..adb96c74c5 100644 --- a/submodules/AsyncDisplayKit/Source/Private/ASDisplayNode+FrameworkPrivate.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h @@ -13,6 +13,7 @@ // #import +#import #import #import @@ -316,8 +317,4 @@ __unused static NSString * _Nonnull NSStringFromASHierarchyStateChange(ASHierarc - (NSArray *)accessibilityElements; @end; -@interface CALayer (ASDisplayNodeInternal) -@property (nullable, weak) ASDisplayNode *asyncdisplaykit_node; -@end - NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+InterfaceState.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode+InterfaceState.h similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASDisplayNode+InterfaceState.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode+InterfaceState.h index 1de83700ac..dfcb52f95d 100644 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode+InterfaceState.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode+InterfaceState.h @@ -7,6 +7,7 @@ // #import +#import /** * Interface state is available on ASDisplayNode and ASViewController, and diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+LayoutSpec.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode+LayoutSpec.h similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASDisplayNode+LayoutSpec.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode+LayoutSpec.h index d7cd862047..a3ae8cb5c5 100644 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode+LayoutSpec.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode+LayoutSpec.h @@ -6,6 +6,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // +#import #import #import diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Subclasses.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode+Subclasses.h similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASDisplayNode+Subclasses.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode+Subclasses.h index d3c9575fbb..5ea0b2e6fe 100644 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Subclasses.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode+Subclasses.h @@ -7,6 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // +#import #import #import #import diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode.h similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASDisplayNode.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode.h index 7d962747d7..5b97e3fd83 100644 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode.h @@ -983,8 +983,14 @@ typedef NS_ENUM(NSInteger, ASLayoutEngineType) { @end +#define ASScopedLockSelfOrToRoot() ASLockScopeSelf() + @interface UIView (ASDisplayNodeInternal) @property (nullable, weak) ASDisplayNode *asyncdisplaykit_node; @end +@interface CALayer (ASDisplayNodeInternal) +@property (nullable, weak) ASDisplayNode *asyncdisplaykit_node; +@end + NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNodeExtras.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNodeExtras.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASDisplayNodeExtras.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNodeExtras.h diff --git a/submodules/AsyncDisplayKit/Source/ASEditableTextNode.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASEditableTextNode.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASEditableTextNode.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASEditableTextNode.h diff --git a/submodules/AsyncDisplayKit/Source/Base/ASEqualityHelpers.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASEqualityHelpers.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/Base/ASEqualityHelpers.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASEqualityHelpers.h diff --git a/submodules/AsyncDisplayKit/Source/ASExperimentalFeatures.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASExperimentalFeatures.h similarity index 98% rename from submodules/AsyncDisplayKit/Source/ASExperimentalFeatures.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASExperimentalFeatures.h index c3d56eb7d8..7b2a160c11 100644 --- a/submodules/AsyncDisplayKit/Source/ASExperimentalFeatures.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASExperimentalFeatures.h @@ -7,6 +7,7 @@ // #import +#import #import #import diff --git a/submodules/AsyncDisplayKit/Source/Details/ASGraphicsContext.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASGraphicsContext.h similarity index 98% rename from submodules/AsyncDisplayKit/Source/Details/ASGraphicsContext.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASGraphicsContext.h index d22d3806b0..1ef56518c9 100644 --- a/submodules/AsyncDisplayKit/Source/Details/ASGraphicsContext.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASGraphicsContext.h @@ -7,6 +7,7 @@ // #import +#import #import #import diff --git a/submodules/AsyncDisplayKit/Source/Details/ASHashing.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASHashing.h similarity index 98% rename from submodules/AsyncDisplayKit/Source/Details/ASHashing.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASHashing.h index 084d74078a..d1393bfec5 100644 --- a/submodules/AsyncDisplayKit/Source/Details/ASHashing.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASHashing.h @@ -7,6 +7,7 @@ // #import +#import #import NS_ASSUME_NONNULL_BEGIN diff --git a/submodules/AsyncDisplayKit/Source/Private/ASInternalHelpers.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASInternalHelpers.h similarity index 86% rename from submodules/AsyncDisplayKit/Source/Private/ASInternalHelpers.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASInternalHelpers.h index 8bc53fa0e6..780566f395 100644 --- a/submodules/AsyncDisplayKit/Source/Private/ASInternalHelpers.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASInternalHelpers.h @@ -7,13 +7,12 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASAvailability.h" +#import #import #import #import -#import NS_ASSUME_NONNULL_BEGIN @@ -97,22 +96,6 @@ ASDISPLAYNODE_INLINE UIEdgeInsets ASConcatInsets(UIEdgeInsets insetsA, UIEdgeIns return insetsA; } -ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASImageDownloaderPriority ASImageDownloaderPriorityWithInterfaceState(ASInterfaceState interfaceState) { - if (ASInterfaceStateIncludesVisible(interfaceState)) { - return ASImageDownloaderPriorityVisible; - } - - if (ASInterfaceStateIncludesDisplay(interfaceState)) { - return ASImageDownloaderPriorityImminent; - } - - if (ASInterfaceStateIncludesPreload(interfaceState)) { - return ASImageDownloaderPriorityPreload; - } - - return ASImageDownloaderPriorityPreload; -} - @interface NSIndexPath (ASInverseComparison) - (NSComparisonResult)asdk_inverseCompare:(NSIndexPath *)otherIndexPath; @end diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASLayout.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASLayout.h similarity index 99% rename from submodules/AsyncDisplayKit/Source/Layout/ASLayout.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASLayout.h index 0e40574d07..7ee6e80fc0 100644 --- a/submodules/AsyncDisplayKit/Source/Layout/ASLayout.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASLayout.h @@ -8,7 +8,9 @@ // #pragma once + #import +#import #import #import diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASLayoutElement.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASLayoutElement.h similarity index 99% rename from submodules/AsyncDisplayKit/Source/Layout/ASLayoutElement.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASLayoutElement.h index c4ba689cfc..b32d9d73e9 100644 --- a/submodules/AsyncDisplayKit/Source/Layout/ASLayoutElement.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASLayoutElement.h @@ -7,6 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // +#import #import #import #import diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASLayoutElementExtensibility.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASLayoutElementExtensibility.h similarity index 99% rename from submodules/AsyncDisplayKit/Source/Layout/ASLayoutElementExtensibility.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASLayoutElementExtensibility.h index f46b63e812..8aae2e3442 100644 --- a/submodules/AsyncDisplayKit/Source/Layout/ASLayoutElementExtensibility.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASLayoutElementExtensibility.h @@ -7,6 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // +#import #import #import diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASLayoutElementPrivate.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASLayoutElementPrivate.h similarity index 99% rename from submodules/AsyncDisplayKit/Source/Layout/ASLayoutElementPrivate.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASLayoutElementPrivate.h index cf699f42a2..6f713f3908 100644 --- a/submodules/AsyncDisplayKit/Source/Layout/ASLayoutElementPrivate.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASLayoutElementPrivate.h @@ -7,6 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // +#import #import #import diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASLayoutSpec.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASLayoutSpec.h similarity index 99% rename from submodules/AsyncDisplayKit/Source/Layout/ASLayoutSpec.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASLayoutSpec.h index c7aa01c386..2056557b05 100644 --- a/submodules/AsyncDisplayKit/Source/Layout/ASLayoutSpec.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASLayoutSpec.h @@ -7,6 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // +#import #import #import #import diff --git a/submodules/AsyncDisplayKit/Source/ASLocking.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASLocking.h similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASLocking.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASLocking.h index 3e284dc26c..c78fcce43d 100644 --- a/submodules/AsyncDisplayKit/Source/ASLocking.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASLocking.h @@ -7,6 +7,7 @@ // #import +#import #import #import diff --git a/submodules/AsyncDisplayKit/Source/ASMainThreadDeallocation.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASMainThreadDeallocation.h similarity index 98% rename from submodules/AsyncDisplayKit/Source/ASMainThreadDeallocation.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASMainThreadDeallocation.h index 391b6bb696..1ac44fb6d4 100644 --- a/submodules/AsyncDisplayKit/Source/ASMainThreadDeallocation.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASMainThreadDeallocation.h @@ -7,6 +7,7 @@ // #import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/submodules/AsyncDisplayKit/Source/Details/ASObjectDescriptionHelpers.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASObjectDescriptionHelpers.h similarity index 98% rename from submodules/AsyncDisplayKit/Source/Details/ASObjectDescriptionHelpers.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASObjectDescriptionHelpers.h index 5a2823bd99..46aa7bacd9 100644 --- a/submodules/AsyncDisplayKit/Source/Details/ASObjectDescriptionHelpers.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASObjectDescriptionHelpers.h @@ -8,6 +8,7 @@ // #import +#import #import NS_ASSUME_NONNULL_BEGIN diff --git a/submodules/AsyncDisplayKit/Source/Details/ASRecursiveUnfairLock.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASRecursiveUnfairLock.h similarity index 98% rename from submodules/AsyncDisplayKit/Source/Details/ASRecursiveUnfairLock.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASRecursiveUnfairLock.h index d705e4d384..7ded8926b3 100644 --- a/submodules/AsyncDisplayKit/Source/Details/ASRecursiveUnfairLock.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASRecursiveUnfairLock.h @@ -7,6 +7,7 @@ // #import +#import #import #import #import diff --git a/submodules/AsyncDisplayKit/Source/ASRunLoopQueue.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASRunLoopQueue.h similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASRunLoopQueue.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASRunLoopQueue.h index 07f3682bbf..ea61b2962a 100644 --- a/submodules/AsyncDisplayKit/Source/ASRunLoopQueue.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASRunLoopQueue.h @@ -8,6 +8,7 @@ // #import +#import #import #import diff --git a/submodules/AsyncDisplayKit/Source/Details/ASScrollDirection.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASScrollDirection.h similarity index 98% rename from submodules/AsyncDisplayKit/Source/Details/ASScrollDirection.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASScrollDirection.h index 41538ed31b..0b7842250a 100644 --- a/submodules/AsyncDisplayKit/Source/Details/ASScrollDirection.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASScrollDirection.h @@ -8,6 +8,7 @@ // #import +#import #import #import diff --git a/submodules/AsyncDisplayKit/Source/ASScrollNode.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASScrollNode.h similarity index 98% rename from submodules/AsyncDisplayKit/Source/ASScrollNode.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASScrollNode.h index 35f6622f1a..6977047fe5 100644 --- a/submodules/AsyncDisplayKit/Source/ASScrollNode.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASScrollNode.h @@ -7,6 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // +#import #import #import diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASStackLayoutDefines.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASStackLayoutDefines.h similarity index 99% rename from submodules/AsyncDisplayKit/Source/Layout/ASStackLayoutDefines.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASStackLayoutDefines.h index 8a86be1b5a..357b7bee2e 100644 --- a/submodules/AsyncDisplayKit/Source/Layout/ASStackLayoutDefines.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASStackLayoutDefines.h @@ -7,6 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // +#import #import /** The direction children are stacked in */ diff --git a/submodules/AsyncDisplayKit/Source/Layout/ASStackLayoutElement.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASStackLayoutElement.h similarity index 98% rename from submodules/AsyncDisplayKit/Source/Layout/ASStackLayoutElement.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASStackLayoutElement.h index 46b263b4ff..246cd34384 100644 --- a/submodules/AsyncDisplayKit/Source/Layout/ASStackLayoutElement.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASStackLayoutElement.h @@ -7,6 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // +#import #import #import diff --git a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitComponents.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASTextKitComponents.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/TextKit/ASTextKitComponents.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASTextKitComponents.h diff --git a/submodules/AsyncDisplayKit/Source/TextKit/ASTextNodeTypes.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASTextNodeTypes.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/TextKit/ASTextNodeTypes.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASTextNodeTypes.h diff --git a/submodules/AsyncDisplayKit/Source/Details/ASThread.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASThread.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/Details/ASThread.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASThread.h diff --git a/submodules/AsyncDisplayKit/Source/Details/ASTraitCollection.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASTraitCollection.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/Details/ASTraitCollection.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASTraitCollection.h diff --git a/submodules/AsyncDisplayKit/Source/Details/ASWeakSet.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASWeakSet.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/Details/ASWeakSet.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASWeakSet.h diff --git a/submodules/AsyncDisplayKit/Source/AsyncDisplayKit.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/AsyncDisplayKit.h similarity index 59% rename from submodules/AsyncDisplayKit/Source/AsyncDisplayKit.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/AsyncDisplayKit.h index 00a0fb37f1..dc00ed8339 100644 --- a/submodules/AsyncDisplayKit/Source/AsyncDisplayKit.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/AsyncDisplayKit.h @@ -22,44 +22,16 @@ #import #import -#import -#import -#import #import -#import - -#import - -#import -#import - -#import - -#import #import -#import -#import -#import -#import -#import - #import #import #import #import #import -#import -#import -#import -#import -#import -#import -#import -#import #import -#import #import #import @@ -68,36 +40,26 @@ #import #import #import -#import #import #import #import #import #import #import -#import #import -#import -#import #import -#import #import -#import #import #import #import #import -#import #import #import -#import -#import #import -#import #import #import #import #import -#import +#import diff --git a/submodules/AsyncDisplayKit/Source/Details/CoreGraphics+ASConvenience.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/CoreGraphics+ASConvenience.h similarity index 97% rename from submodules/AsyncDisplayKit/Source/Details/CoreGraphics+ASConvenience.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/CoreGraphics+ASConvenience.h index a0805f1701..da8c6273d9 100644 --- a/submodules/AsyncDisplayKit/Source/Details/CoreGraphics+ASConvenience.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/CoreGraphics+ASConvenience.h @@ -8,7 +8,7 @@ // #import - +#import #import #import diff --git a/submodules/AsyncDisplayKit/Source/Details/NSArray+Diffing.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/NSArray+Diffing.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/Details/NSArray+Diffing.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/NSArray+Diffing.h diff --git a/submodules/AsyncDisplayKit/Source/UIResponder+AsyncDisplayKit.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/UIResponder+AsyncDisplayKit.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/UIResponder+AsyncDisplayKit.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/UIResponder+AsyncDisplayKit.h diff --git a/submodules/AsyncDisplayKit/Source/Details/UIView+ASConvenience.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/UIView+ASConvenience.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/Details/UIView+ASConvenience.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/UIView+ASConvenience.h diff --git a/submodules/AsyncDisplayKit/Source/Details/Transactions/_ASAsyncTransaction.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/_ASAsyncTransaction.h similarity index 99% rename from submodules/AsyncDisplayKit/Source/Details/Transactions/_ASAsyncTransaction.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/_ASAsyncTransaction.h index 245c62b295..dd44b027a4 100644 --- a/submodules/AsyncDisplayKit/Source/Details/Transactions/_ASAsyncTransaction.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/_ASAsyncTransaction.h @@ -8,6 +8,7 @@ // #import +#import #import NS_ASSUME_NONNULL_BEGIN diff --git a/submodules/AsyncDisplayKit/Source/Details/Transactions/_ASAsyncTransactionContainer.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/_ASAsyncTransactionContainer.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/Details/Transactions/_ASAsyncTransactionContainer.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/_ASAsyncTransactionContainer.h diff --git a/submodules/AsyncDisplayKit/Source/Details/Transactions/_ASAsyncTransactionGroup.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/_ASAsyncTransactionGroup.h similarity index 97% rename from submodules/AsyncDisplayKit/Source/Details/Transactions/_ASAsyncTransactionGroup.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/_ASAsyncTransactionGroup.h index cd0b216c06..bda8e4170c 100644 --- a/submodules/AsyncDisplayKit/Source/Details/Transactions/_ASAsyncTransactionGroup.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/_ASAsyncTransactionGroup.h @@ -8,6 +8,7 @@ // #import +#import #import NS_ASSUME_NONNULL_BEGIN diff --git a/submodules/AsyncDisplayKit/Source/Private/_ASCoreAnimationExtras.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/_ASCoreAnimationExtras.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/Private/_ASCoreAnimationExtras.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/_ASCoreAnimationExtras.h diff --git a/submodules/AsyncDisplayKit/Source/Details/_ASDisplayLayer.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/_ASDisplayLayer.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/Details/_ASDisplayLayer.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/_ASDisplayLayer.h diff --git a/submodules/AsyncDisplayKit/Source/Details/_ASDisplayView.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/_ASDisplayView.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/Details/_ASDisplayView.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/_ASDisplayView.h diff --git a/submodules/AsyncDisplayKit/Source/_ASTransitionContext.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/_ASTransitionContext.h similarity index 98% rename from submodules/AsyncDisplayKit/Source/_ASTransitionContext.h rename to submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/_ASTransitionContext.h index 44c4906c39..71cac29736 100644 --- a/submodules/AsyncDisplayKit/Source/_ASTransitionContext.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/_ASTransitionContext.h @@ -8,6 +8,7 @@ // #import +#import #import diff --git a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitAttributes.h b/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitAttributes.h deleted file mode 100644 index d8c21dbc23..0000000000 --- a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitAttributes.h +++ /dev/null @@ -1,128 +0,0 @@ -// -// ASTextKitAttributes.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#pragma once - -#import - -#import - -#if AS_ENABLE_TEXTNODE - -#import - -AS_EXTERN NSString *const ASTextKitTruncationAttributeName; -/** - Use ASTextKitEntityAttribute as the value of this attribute to embed a link or other interactable content inside the - text. - */ -AS_EXTERN NSString *const ASTextKitEntityAttributeName; - -/** - All NSObject values in this struct should be copied when passed into the TextComponent. - */ -struct ASTextKitAttributes { - /** - The string to be drawn. ASTextKit will not augment this string with default colors, etc. so this must be complete. - */ - NSAttributedString *attributedString; - /** - The string to use as the truncation string, usually just "...". If you have a range of text you would like to - restrict highlighting to (for instance if you have "... Continue Reading", use the ASTextKitTruncationAttributeName - to mark the specific range of the string that should be highlightable. - */ - NSAttributedString *truncationAttributedString; - /** - This is the character set that ASTextKit should attempt to avoid leaving as a trailing character before your - truncation token. By default this set includes "\s\t\n\r.,!?:;" so you don't end up with ugly looking truncation - text like "Hey, this is some fancy Truncation!\n\n...". Instead it would be truncated as "Hey, this is some fancy - truncation...". This is not always possible. - - Set this to the empty charset if you want to just use the "dumb" truncation behavior. A nil value will be - substituted with the default described above. - */ - NSCharacterSet *avoidTailTruncationSet; - /** - The line-break mode to apply to the text. Since this also impacts how TextKit will attempt to truncate the text - in your string, we only support NSLineBreakByWordWrapping and NSLineBreakByCharWrapping. - */ - NSLineBreakMode lineBreakMode; - /** - The maximum number of lines to draw in the drawable region. Leave blank or set to 0 to define no maximum. - This is required to apply scale factors to shrink text to fit within a number of lines - */ - NSUInteger maximumNumberOfLines; - /** - An array of UIBezierPath objects representing the exclusion paths inside the receiver's bounding rectangle. Default value: nil. - */ - NSArray *exclusionPaths; - /** - The shadow offset for any shadows applied to the text. The coordinate space for this is the same as UIKit, so a - positive width means towards the right, and a positive height means towards the bottom. - */ - CGSize shadowOffset; - /** - The color to use in drawing the text's shadow. - */ - UIColor *shadowColor; - /** - The opacity of the shadow from 0 to 1. - */ - CGFloat shadowOpacity; - /** - The radius that should be applied to the shadow blur. Larger values mean a larger, more blurred shadow. - */ - CGFloat shadowRadius; - /** - An array of scale factors in descending order to apply to the text to try to make it fit into a constrained size. - */ - NSArray *pointSizeScaleFactors; - - /** - We provide an explicit copy function so we can use aggregate initializer syntax while providing copy semantics for - the NSObjects inside. - */ - const ASTextKitAttributes copy() const - { - return { - [attributedString copy], - [truncationAttributedString copy], - [avoidTailTruncationSet copy], - lineBreakMode, - maximumNumberOfLines, - [exclusionPaths copy], - shadowOffset, - [shadowColor copy], - shadowOpacity, - shadowRadius, - pointSizeScaleFactors, - }; - }; - - bool operator==(const ASTextKitAttributes &other) const - { - // These comparisons are in a specific order to reduce the overall cost of this function. - return lineBreakMode == other.lineBreakMode - && maximumNumberOfLines == other.maximumNumberOfLines - && shadowOpacity == other.shadowOpacity - && shadowRadius == other.shadowRadius - && (pointSizeScaleFactors == other.pointSizeScaleFactors - || [pointSizeScaleFactors isEqualToArray:other.pointSizeScaleFactors]) - && CGSizeEqualToSize(shadowOffset, other.shadowOffset) - && ASObjectIsEqual(exclusionPaths, other.exclusionPaths) - && ASObjectIsEqual(avoidTailTruncationSet, other.avoidTailTruncationSet) - && ASObjectIsEqual(shadowColor, other.shadowColor) - && ASObjectIsEqual(attributedString, other.attributedString) - && ASObjectIsEqual(truncationAttributedString, other.truncationAttributedString); - } - - size_t hash() const; -}; - -#endif diff --git a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitAttributes.mm b/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitAttributes.mm deleted file mode 100644 index 400ef437bb..0000000000 --- a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitAttributes.mm +++ /dev/null @@ -1,50 +0,0 @@ -// -// ASTextKitAttributes.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_ENABLE_TEXTNODE - -#import - -NSString *const ASTextKitTruncationAttributeName = @"ck_truncation"; -NSString *const ASTextKitEntityAttributeName = @"ck_entity"; - -size_t ASTextKitAttributes::hash() const -{ -#pragma clang diagnostic push -#pragma clang diagnostic warning "-Wpadded" - struct { - NSUInteger attrStringHash; - NSUInteger truncationStringHash; - NSUInteger avoidTrunactionSetHash; - NSLineBreakMode lineBreakMode; - NSUInteger maximumNumberOfLines; - NSUInteger exclusionPathsHash; - CGSize shadowOffset; - NSUInteger shadowColorHash; - CGFloat shadowOpacity; - CGFloat shadowRadius; -#pragma clang diagnostic pop - } data = { - [attributedString hash], - [truncationAttributedString hash], - [avoidTailTruncationSet hash], - lineBreakMode, - maximumNumberOfLines, - [exclusionPaths hash], - shadowOffset, - [shadowColor hash], - shadowOpacity, - shadowRadius, - }; - return ASHashBytes(&data, sizeof(data)); -} - -#endif diff --git a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitCoreTextAdditions.h b/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitCoreTextAdditions.h deleted file mode 100644 index 9a8f48050a..0000000000 --- a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitCoreTextAdditions.h +++ /dev/null @@ -1,89 +0,0 @@ -// -// ASTextKitCoreTextAdditions.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -#if AS_ENABLE_TEXTNODE - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - @abstract Returns whether a given attribute is an unsupported Core Text attribute. - @param attributeName The name of the attribute - @discussion The following Core Text attributes are not supported on NSAttributedString, and thus will not be preserved during the conversion: - - kCTForegroundColorFromContextAttributeName - - kCTSuperscriptAttributeName - - kCTGlyphInfoAttributeName - - kCTCharacterShapeAttributeName - - kCTLanguageAttributeName - - kCTRunDelegateAttributeName - - kCTBaselineClassAttributeName - - kCTBaselineInfoAttributeName - - kCTBaselineReferenceInfoAttributeName - - kCTWritingDirectionAttributeName - - kCTUnderlineColorAttributeName - @result Whether attributeName is an unsupported Core Text attribute. - */ -AS_EXTERN BOOL ASAttributeWithNameIsUnsupportedCoreTextAttribute(NSString *attributeName); - - -/** - @abstract Returns an attributes dictionary for use by NSAttributedString, given a dictionary of Core Text attributes. - @param coreTextAttributes An NSDictionary whose keys are CFAttributedStringRef attributes. - @discussion The following Core Text attributes are not supported on NSAttributedString, and thus will not be preserved during the conversion: - - kCTForegroundColorFromContextAttributeName - - kCTSuperscriptAttributeName - - kCTGlyphInfoAttributeName - - kCTCharacterShapeAttributeName - - kCTLanguageAttributeName - - kCTRunDelegateAttributeName - - kCTBaselineClassAttributeName - - kCTBaselineInfoAttributeName - - kCTBaselineReferenceInfoAttributeName - - kCTWritingDirectionAttributeName - - kCTUnderlineColorAttributeName - @result An NSDictionary of attributes for use by NSAttributedString. - */ -AS_EXTERN NSDictionary *NSAttributedStringAttributesForCoreTextAttributes(NSDictionary *coreTextAttributes); - -/** - @abstract Returns an NSAttributedString whose Core Text attributes have been converted, where possible, to NSAttributedString attributes. - @param dirtyAttributedString An NSAttributedString that may contain Core Text attributes. - @result An NSAttributedString that's preserved as many CFAttributedString attributes as possible. - */ -AS_EXTERN NSAttributedString *ASCleanseAttributedStringOfCoreTextAttributes(NSAttributedString *dirtyAttributedString); - -#pragma mark - -#pragma mark - -@interface NSParagraphStyle (ASTextKitCoreTextAdditions) - -/** - @abstract Returns an NSParagraphStyle initialized with the paragraph specifiers from the given CTParagraphStyleRef. - @param coreTextParagraphStyle A Core Text paragraph style. - @discussion It is important to note that not all CTParagraphStyle specifiers are supported by NSParagraphStyle, and consequently, this is a lossy conversion. Notably, the following specifiers will not preserved: - - kCTParagraphStyleSpecifierTabStops - - kCTParagraphStyleSpecifierDefaultTabInterval - - kCTParagraphStyleSpecifierMaximumLineSpacing - - kCTParagraphStyleSpecifierMinimumLineSpacing - - kCTParagraphStyleSpecifierLineSpacingAdjustment - - kCTParagraphStyleSpecifierLineBoundsOptions - @result An NSParagraphStyle initialized with as many of the paragraph specifiers from `coreTextParagraphStyle` as possible. - - */ -+ (NSParagraphStyle *)paragraphStyleWithCTParagraphStyle:(CTParagraphStyleRef)coreTextParagraphStyle NS_RETURNS_RETAINED; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitCoreTextAdditions.mm b/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitCoreTextAdditions.mm deleted file mode 100644 index 8ae370de14..0000000000 --- a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitCoreTextAdditions.mm +++ /dev/null @@ -1,331 +0,0 @@ -// -// ASTextKitCoreTextAdditions.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_ENABLE_TEXTNODE - -#import -#import - -#import - -#pragma mark - Public -BOOL ASAttributeWithNameIsUnsupportedCoreTextAttribute(NSString *attributeName) -{ - static NSSet *coreTextAttributes; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - coreTextAttributes = [NSSet setWithObjects:(__bridge id)kCTForegroundColorAttributeName, - kCTForegroundColorFromContextAttributeName, - kCTForegroundColorAttributeName, - kCTStrokeColorAttributeName, - kCTUnderlineStyleAttributeName, - kCTVerticalFormsAttributeName, - kCTRunDelegateAttributeName, - kCTBaselineClassAttributeName, - kCTBaselineInfoAttributeName, - kCTBaselineReferenceInfoAttributeName, - kCTUnderlineColorAttributeName, - kCTParagraphStyleAttributeName, - nil]; - }); - return [coreTextAttributes containsObject:attributeName]; -} - -NSDictionary *NSAttributedStringAttributesForCoreTextAttributes(NSDictionary *coreTextAttributes) -{ - NSMutableDictionary *cleanAttributes = [[NSMutableDictionary alloc] initWithCapacity:coreTextAttributes.count]; - - [coreTextAttributes enumerateKeysAndObjectsUsingBlock:^(NSString *coreTextKey, id coreTextValue, BOOL *stop) { - // The following attributes are not supported on NSAttributedString. Should they become available, we should add them. - /* - kCTForegroundColorFromContextAttributeName - kCTSuperscriptAttributeName - kCTGlyphInfoAttributeName - kCTCharacterShapeAttributeName - kCTLanguageAttributeName - kCTRunDelegateAttributeName - kCTBaselineClassAttributeName - kCTBaselineInfoAttributeName - kCTBaselineReferenceInfoAttributeName - kCTWritingDirectionAttributeName - kCTUnderlineColorAttributeName - */ - - // Conversely, the following attributes are not supported on CFAttributedString. Should they become available, we should add them. - /* - NSStrikethroughStyleAttributeName - NSShadowAttributeName - NSBackgroundColorAttributeName - */ - - // kCTFontAttributeName -> NSFontAttributeName - if ([coreTextKey isEqualToString:(NSString *)kCTFontAttributeName]) { - CTFontRef coreTextFont = (__bridge CTFontRef)coreTextValue; - cleanAttributes[NSFontAttributeName] = (__bridge UIFont *)coreTextFont; - } - // kCTKernAttributeName -> NSKernAttributeName - else if ([coreTextKey isEqualToString:(NSString *)kCTKernAttributeName]) { - cleanAttributes[NSKernAttributeName] = (NSNumber *)coreTextValue; - } - // kCTLigatureAttributeName -> NSLigatureAttributeName - else if ([coreTextKey isEqualToString:(NSString *)kCTLigatureAttributeName]) { - cleanAttributes[NSLigatureAttributeName] = (NSNumber *)coreTextValue; - } - // kCTForegroundColorAttributeName -> NSForegroundColorAttributeName - else if ([coreTextKey isEqualToString:(NSString *)kCTForegroundColorAttributeName]) { - cleanAttributes[NSForegroundColorAttributeName] = [UIColor colorWithCGColor:(CGColorRef)coreTextValue]; - } - // kCTParagraphStyleAttributeName -> NSParagraphStyleAttributeName - else if ([coreTextKey isEqualToString:(NSString *)kCTParagraphStyleAttributeName]) { - if ([coreTextValue isKindOfClass:[NSParagraphStyle class]]) { - cleanAttributes[NSParagraphStyleAttributeName] = (NSParagraphStyle *)coreTextValue; - } - else { - cleanAttributes[NSParagraphStyleAttributeName] = [NSParagraphStyle paragraphStyleWithCTParagraphStyle:(CTParagraphStyleRef)coreTextValue]; - } - } - // kCTStrokeWidthAttributeName -> NSStrokeWidthAttributeName - else if ([coreTextKey isEqualToString:(NSString *)kCTStrokeWidthAttributeName]) { - cleanAttributes[NSStrokeWidthAttributeName] = (NSNumber *)coreTextValue; - } - // kCTStrokeColorAttributeName -> NSStrokeColorAttributeName - else if ([coreTextKey isEqualToString:(NSString *)kCTStrokeColorAttributeName]) { - cleanAttributes[NSStrokeColorAttributeName] = [UIColor colorWithCGColor:(CGColorRef)coreTextValue]; - } - // kCTUnderlineStyleAttributeName -> NSUnderlineStyleAttributeName - else if ([coreTextKey isEqualToString:(NSString *)kCTUnderlineStyleAttributeName]) { - cleanAttributes[NSUnderlineStyleAttributeName] = (NSNumber *)coreTextValue; - } - // kCTVerticalFormsAttributeName -> NSVerticalGlyphFormAttributeName - else if ([coreTextKey isEqualToString:(NSString *)kCTVerticalFormsAttributeName]) { - BOOL flag = (BOOL)CFBooleanGetValue((CFBooleanRef)coreTextValue); - cleanAttributes[NSVerticalGlyphFormAttributeName] = @((int)flag); // NSVerticalGlyphFormAttributeName is documented to be an NSNumber with an integer that's either 0 or 1. - } - // Don't filter out any internal text attributes - else if (!ASAttributeWithNameIsUnsupportedCoreTextAttribute(coreTextKey)){ - cleanAttributes[coreTextKey] = coreTextValue; - } - }]; - - return cleanAttributes; -} - -NSAttributedString *ASCleanseAttributedStringOfCoreTextAttributes(NSAttributedString *dirtyAttributedString) -{ - if (!dirtyAttributedString) - return nil; - - // First see if there are any core text attributes on the string - __block BOOL containsCoreTextAttributes = NO; - [dirtyAttributedString enumerateAttributesInRange:NSMakeRange(0, dirtyAttributedString.length) - options:0 - usingBlock:^(NSDictionary *dirtyAttributes, NSRange range, BOOL *stop) { - [dirtyAttributes enumerateKeysAndObjectsUsingBlock:^(NSString *coreTextKey, id coreTextValue, BOOL *innerStop) { - if (ASAttributeWithNameIsUnsupportedCoreTextAttribute(coreTextKey)) { - containsCoreTextAttributes = YES; - *innerStop = YES; - } - }]; - *stop = containsCoreTextAttributes; - }]; - if (containsCoreTextAttributes) { - - NSString *plainString = dirtyAttributedString.string; - NSMutableAttributedString *cleanAttributedString = [[NSMutableAttributedString alloc] initWithString:plainString]; - - // Iterate over all of the attributes, cleaning them as appropriate and applying them as we go. - [dirtyAttributedString enumerateAttributesInRange:NSMakeRange(0, plainString.length) - options:0 - usingBlock:^(NSDictionary *dirtyAttributes, NSRange range, BOOL *stop) { - [cleanAttributedString addAttributes:NSAttributedStringAttributesForCoreTextAttributes(dirtyAttributes) range:range]; - }]; - - return cleanAttributedString; - } else { - return [dirtyAttributedString copy]; - } -} - -#pragma mark - -#pragma mark - -@implementation NSParagraphStyle (ASTextKitCoreTextAdditions) - -+ (NSParagraphStyle *)paragraphStyleWithCTParagraphStyle:(CTParagraphStyleRef)coreTextParagraphStyle NS_RETURNS_RETAINED -{ - NSMutableParagraphStyle *newParagraphStyle = [[NSMutableParagraphStyle alloc] init]; - - if (!coreTextParagraphStyle) { - return newParagraphStyle; - } - - // The following paragraph style specifiers are not supported on NSParagraphStyle. Should they become available, we should add them. - /* - kCTParagraphStyleSpecifierTabStops - kCTParagraphStyleSpecifierDefaultTabInterval - kCTParagraphStyleSpecifierMaximumLineSpacing - kCTParagraphStyleSpecifierMinimumLineSpacing - kCTParagraphStyleSpecifierLineSpacingAdjustment - kCTParagraphStyleSpecifierLineBoundsOptions - */ - - // Conversely, the following paragraph styles are not supported on CTParagraphStyle. Should they become available, we should add them. - /* - hyphenationFactor - */ - - // kCTParagraphStyleSpecifierAlignment -> alignment - CTTextAlignment coreTextAlignment; - if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, - kCTParagraphStyleSpecifierAlignment, - sizeof(coreTextAlignment), - &coreTextAlignment)) { - newParagraphStyle.alignment = NSTextAlignmentFromCTTextAlignment(coreTextAlignment); - } - - // kCTParagraphStyleSpecifierFirstLineHeadIndent -> firstLineHeadIndent - CGFloat firstLineHeadIndent; - if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, - kCTParagraphStyleSpecifierFirstLineHeadIndent, - sizeof(firstLineHeadIndent), - &firstLineHeadIndent)) { - newParagraphStyle.firstLineHeadIndent = firstLineHeadIndent; - } - - // kCTParagraphStyleSpecifierHeadIndent -> headIndent - CGFloat headIndent; - if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, - kCTParagraphStyleSpecifierHeadIndent, - sizeof(headIndent), - &headIndent)) { - newParagraphStyle.headIndent = headIndent; - } - - // kCTParagraphStyleSpecifierTailIndent -> tailIndent - CGFloat tailIndent; - if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, - kCTParagraphStyleSpecifierTailIndent, - sizeof(tailIndent), - &tailIndent)) { - newParagraphStyle.tailIndent = tailIndent; - } - - // kCTParagraphStyleSpecifierLineBreakMode -> lineBreakMode - CTLineBreakMode coreTextLineBreakMode; - if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, - kCTParagraphStyleSpecifierLineBreakMode, - sizeof(coreTextLineBreakMode), - &coreTextLineBreakMode)) { - newParagraphStyle.lineBreakMode = (NSLineBreakMode)coreTextLineBreakMode; // They're the same enum. - } - - // kCTParagraphStyleSpecifierLineHeightMultiple -> lineHeightMultiple - CGFloat lineHeightMultiple; - if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, - kCTParagraphStyleSpecifierLineHeightMultiple, - sizeof(lineHeightMultiple), - &lineHeightMultiple)) { - newParagraphStyle.lineHeightMultiple = lineHeightMultiple; - } - - // kCTParagraphStyleSpecifierMaximumLineHeight -> maximumLineHeight - CGFloat maximumLineHeight; - if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, - kCTParagraphStyleSpecifierMaximumLineHeight, - sizeof(maximumLineHeight), - &maximumLineHeight)) { - newParagraphStyle.maximumLineHeight = maximumLineHeight; - } - - // kCTParagraphStyleSpecifierMinimumLineHeight -> minimumLineHeight - CGFloat minimumLineHeight; - if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, - kCTParagraphStyleSpecifierMinimumLineHeight, - sizeof(minimumLineHeight), - &minimumLineHeight)) { - newParagraphStyle.minimumLineHeight = minimumLineHeight; - } - - CGFloat lineSpacing = 0; -#if TARGET_OS_IOS -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - // kCTParagraphStyleSpecifierLineSpacing -> lineSpacing - // Note that kCTParagraphStyleSpecifierLineSpacing is deprecated and will die soon. We should not be using it. - if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, - kCTParagraphStyleSpecifierLineSpacing, - sizeof(lineSpacing), - &lineSpacing)) { - newParagraphStyle.lineSpacing = lineSpacing; - } -#pragma clang diagnostic pop -#endif - - // Attempt to weakly map the following onto -[NSParagraphStyle lineSpacing]: - // - kCTParagraphStyleSpecifierMinimumLineSpacing - // - kCTParagraphStyleSpecifierMaximumLineSpacing - // - kCTParagraphStyleSpecifierLineSpacingAdjustment - if (fabs(lineSpacing) <= FLT_EPSILON && - CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, - kCTParagraphStyleSpecifierMinimumLineSpacing, - sizeof(lineSpacing), - &lineSpacing)) { - newParagraphStyle.lineSpacing = lineSpacing; - } - - if (fabs(lineSpacing) <= FLT_EPSILON && - CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, - kCTParagraphStyleSpecifierMaximumLineSpacing, - sizeof(lineSpacing), - &lineSpacing)) { - newParagraphStyle.lineSpacing = lineSpacing; - } - - if (fabs(lineSpacing) <= FLT_EPSILON && - CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, - kCTParagraphStyleSpecifierLineSpacingAdjustment, - sizeof(lineSpacing), - &lineSpacing)) { - newParagraphStyle.lineSpacing = lineSpacing; - } - - // kCTParagraphStyleSpecifierParagraphSpacing -> paragraphSpacing - CGFloat paragraphSpacing; - if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, - kCTParagraphStyleSpecifierParagraphSpacing, - sizeof(paragraphSpacing), - ¶graphSpacing)) { - newParagraphStyle.paragraphSpacing = paragraphSpacing; - } - - // kCTParagraphStyleSpecifierParagraphSpacingBefore -> paragraphSpacingBefore - CGFloat paragraphSpacingBefore; - if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, - kCTParagraphStyleSpecifierParagraphSpacingBefore, - sizeof(paragraphSpacingBefore), - ¶graphSpacingBefore)) { - newParagraphStyle.paragraphSpacingBefore = paragraphSpacingBefore; - } - - // kCTParagraphStyleSpecifierBaseWritingDirection -> baseWritingDirection - CTWritingDirection coreTextBaseWritingDirection; - if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, - kCTParagraphStyleSpecifierBaseWritingDirection, - sizeof(coreTextBaseWritingDirection), - &coreTextBaseWritingDirection)) { - newParagraphStyle.baseWritingDirection = (NSWritingDirection)coreTextBaseWritingDirection; // They're the same enum. - } - - return newParagraphStyle; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitEntityAttribute.h b/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitEntityAttribute.h deleted file mode 100644 index 3655138be7..0000000000 --- a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitEntityAttribute.h +++ /dev/null @@ -1,36 +0,0 @@ -// -// ASTextKitEntityAttribute.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -#if AS_ENABLE_TEXTNODE - -#import - -/** - The object that should be embedded with ASTextKitEntityAttributeName. Please note that the entity you provide MUST - implement a proper hash and isEqual function or your application performance will grind to a halt due to - NSMutableAttributedString's usage of a global hash table of all attributes. This means the entity should NOT be a - Foundation Collection (NSArray, NSDictionary, NSSet, etc.) since their hash function is a simple count of the values - in the collection, which causes pathological performance problems deep inside NSAttributedString's implementation. - - rdar://19352367 - */ -AS_SUBCLASSING_RESTRICTED -@interface ASTextKitEntityAttribute : NSObject - -@property (nonatomic, readonly) id entity; - -- (instancetype)initWithEntity:(id)entity; - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitEntityAttribute.mm b/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitEntityAttribute.mm deleted file mode 100644 index fb87e9bd3a..0000000000 --- a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitEntityAttribute.mm +++ /dev/null @@ -1,43 +0,0 @@ -// -// ASTextKitEntityAttribute.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_ENABLE_TEXTNODE - -@implementation ASTextKitEntityAttribute - -- (instancetype)initWithEntity:(id)entity -{ - if (self = [super init]) { - _entity = entity; - } - return self; -} - -- (NSUInteger)hash -{ - return [_entity hash]; -} - -- (BOOL)isEqual:(id)object -{ - if (self == object) { - return YES; - } - if (![object isKindOfClass:[self class]]) { - return NO; - } - ASTextKitEntityAttribute *other = (ASTextKitEntityAttribute *)object; - return _entity == other.entity || [_entity isEqual:other.entity]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitFontSizeAdjuster.h b/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitFontSizeAdjuster.h deleted file mode 100644 index 1b7b10ff5f..0000000000 --- a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitFontSizeAdjuster.h +++ /dev/null @@ -1,58 +0,0 @@ -// -// ASTextKitFontSizeAdjuster.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -#if AS_ENABLE_TEXTNODE - -#import - -NS_ASSUME_NONNULL_BEGIN - -@class ASTextKitContext; - -AS_SUBCLASSING_RESTRICTED -@interface ASTextKitFontSizeAdjuster : NSObject - -@property (nonatomic) CGSize constrainedSize; - -/** - * Creates a class that will return a scale factor the will make a string fit inside the constrained size. - * - * "Fitting" means that both the longest word in the string will fit without breaking in the constrained - * size's width AND that the entire string will try to fit within attribute's maximumLineCount. The amount - * that the string will scale is based upon the attribute's pointSizeScaleFactors. If the string cannot fit - * in the given width/number of lines, the smallest scale factor will be returned. - * - * @param context The text kit context - * @param constrainedSize The constrained size to render into - * @param textComponentAttributes The renderer's text attributes - */ -- (instancetype)initWithContext:(ASTextKitContext *)context - constrainedSize:(CGSize)constrainedSize - textKitAttributes:(const ASTextKitAttributes &)textComponentAttributes; - -/** - * Returns the best fit scale factor for the text - */ -- (CGFloat)scaleFactor; - -/** - * Takes all of the attributed string attributes dealing with size (font size, line spacing, kerning, etc) and - * scales them by the scaleFactor. I wouldn't be surprised if I missed some in here. - */ -+ (void)adjustFontSizeForAttributeString:(NSMutableAttributedString *)attrString withScaleFactor:(CGFloat)scaleFactor; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitFontSizeAdjuster.mm b/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitFontSizeAdjuster.mm deleted file mode 100644 index aeea44d7cc..0000000000 --- a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitFontSizeAdjuster.mm +++ /dev/null @@ -1,241 +0,0 @@ -// -// ASTextKitFontSizeAdjuster.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - - -#import - -#if AS_ENABLE_TEXTNODE - -#import -#import - -#import -#import -#import - -//#define LOG(...) NSLog(__VA_ARGS__) -#define LOG(...) - -@interface ASTextKitFontSizeAdjuster() -@property (nonatomic, readonly) NSLayoutManager *sizingLayoutManager; -@property (nonatomic, readonly) NSTextContainer *sizingTextContainer; -@end - -@implementation ASTextKitFontSizeAdjuster -{ - __weak ASTextKitContext *_context; - ASTextKitAttributes _attributes; - BOOL _measured; - CGFloat _scaleFactor; - AS::Mutex __instanceLock__; -} - -@synthesize sizingLayoutManager = _sizingLayoutManager; -@synthesize sizingTextContainer = _sizingTextContainer; - -- (instancetype)initWithContext:(ASTextKitContext *)context - constrainedSize:(CGSize)constrainedSize - textKitAttributes:(const ASTextKitAttributes &)textComponentAttributes; -{ - if (self = [super init]) { - _context = context; - _constrainedSize = constrainedSize; - _attributes = textComponentAttributes; - } - return self; -} - -+ (void)adjustFontSizeForAttributeString:(NSMutableAttributedString *)attrString withScaleFactor:(CGFloat)scaleFactor -{ - if (scaleFactor == 1.0) return; - - [attrString beginEditing]; - - // scale all the attributes that will change the bounding box - [attrString enumerateAttributesInRange:NSMakeRange(0, attrString.length) options:0 usingBlock:^(NSDictionary * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) { - if (attrs[NSFontAttributeName] != nil) { - UIFont *font = attrs[NSFontAttributeName]; - font = [font fontWithSize:std::round(font.pointSize * scaleFactor)]; - [attrString removeAttribute:NSFontAttributeName range:range]; - [attrString addAttribute:NSFontAttributeName value:font range:range]; - } - - if (attrs[NSKernAttributeName] != nil) { - NSNumber *kerning = attrs[NSKernAttributeName]; - [attrString removeAttribute:NSKernAttributeName range:range]; - [attrString addAttribute:NSKernAttributeName value:@([kerning floatValue] * scaleFactor) range:range]; - } - - if (attrs[NSParagraphStyleAttributeName] != nil) { - NSMutableParagraphStyle *paragraphStyle = [attrs[NSParagraphStyleAttributeName] mutableCopy]; - paragraphStyle.lineSpacing = (paragraphStyle.lineSpacing * scaleFactor); - paragraphStyle.paragraphSpacing = (paragraphStyle.paragraphSpacing * scaleFactor); - paragraphStyle.firstLineHeadIndent = (paragraphStyle.firstLineHeadIndent * scaleFactor); - paragraphStyle.headIndent = (paragraphStyle.headIndent * scaleFactor); - paragraphStyle.tailIndent = (paragraphStyle.tailIndent * scaleFactor); - paragraphStyle.minimumLineHeight = (paragraphStyle.minimumLineHeight * scaleFactor); - paragraphStyle.maximumLineHeight = (paragraphStyle.maximumLineHeight * scaleFactor); - - [attrString removeAttribute:NSParagraphStyleAttributeName range:range]; - [attrString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range]; - } - - }]; - - [attrString endEditing]; -} - -- (NSUInteger)lineCountForString:(NSAttributedString *)attributedString -{ - NSUInteger lineCount = 0; - - NSLayoutManager *sizingLayoutManager = [self sizingLayoutManager]; - NSTextContainer *sizingTextContainer = [self sizingTextContainer]; - - NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString]; - [textStorage addLayoutManager:sizingLayoutManager]; - - [sizingLayoutManager ensureLayoutForTextContainer:sizingTextContainer]; - for (NSRange lineRange = { 0, 0 }; NSMaxRange(lineRange) < [sizingLayoutManager numberOfGlyphs] && lineCount <= _attributes.maximumNumberOfLines; lineCount++) { - [sizingLayoutManager lineFragmentRectForGlyphAtIndex:NSMaxRange(lineRange) effectiveRange:&lineRange]; - } - - [textStorage removeLayoutManager:sizingLayoutManager]; - return lineCount; -} - -- (CGSize)boundingBoxForString:(NSAttributedString *)attributedString -{ - NSLayoutManager *sizingLayoutManager = [self sizingLayoutManager]; - NSTextContainer *sizingTextContainer = [self sizingTextContainer]; - - NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString]; - [textStorage addLayoutManager:sizingLayoutManager]; - - [sizingLayoutManager ensureLayoutForTextContainer:sizingTextContainer]; - CGRect textRect = [sizingLayoutManager boundingRectForGlyphRange:NSMakeRange(0, [textStorage length]) - inTextContainer:sizingTextContainer]; - [textStorage removeLayoutManager:sizingLayoutManager]; - return textRect.size; -} - -- (NSLayoutManager *)sizingLayoutManager -{ - AS::MutexLocker l(__instanceLock__); - if (_sizingLayoutManager == nil) { - _sizingLayoutManager = [[ASLayoutManager alloc] init]; - _sizingLayoutManager.usesFontLeading = NO; - - if (_sizingTextContainer == nil) { - // make this text container unbounded in height so that the layout manager will compute the total - // number of lines and not stop counting when height runs out. - _sizingTextContainer = [[NSTextContainer alloc] initWithSize:CGSizeMake(_constrainedSize.width, CGFLOAT_MAX)]; - _sizingTextContainer.lineFragmentPadding = 0; - - // use 0 regardless of what is in the attributes so that we get an accurate line count - _sizingTextContainer.maximumNumberOfLines = 0; - - _sizingTextContainer.lineBreakMode = _attributes.lineBreakMode; - _sizingTextContainer.exclusionPaths = _attributes.exclusionPaths; - } - [_sizingLayoutManager addTextContainer:_sizingTextContainer]; - } - - return _sizingLayoutManager; -} - -- (CGFloat)scaleFactor -{ - if (_measured) { - return _scaleFactor; - } - - if ([_attributes.pointSizeScaleFactors count] == 0 || isinf(_constrainedSize.width)) { - _measured = YES; - _scaleFactor = 1.0; - return _scaleFactor; - } - - __block CGFloat adjustedScale = 1.0; - - // We add the scale factor of 1 to our scaleFactors array so that in the first iteration of the loop below, we are - // actually determining if we need to scale at all. If something doesn't fit, we will continue to iterate our scale factors. - NSArray *scaleFactors = [@[@(1)] arrayByAddingObjectsFromArray:_attributes.pointSizeScaleFactors]; - - [_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { - - // Check for two different situations (and correct for both) - // 1. The longest word in the string fits without being wrapped - // 2. The entire text fits in the given constrained size. - - NSString *str = textStorage.string; - NSArray *words = [str componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - NSString *longestWordNeedingResize = @""; - for (NSString *word in words) { - if ([word length] > [longestWordNeedingResize length]) { - longestWordNeedingResize = word; - } - } - - // check to see if we may need to shrink for any of these things - BOOL longestWordFits = [longestWordNeedingResize length] ? NO : YES; - BOOL maxLinesFits = self->_attributes.maximumNumberOfLines > 0 ? NO : YES; - BOOL heightFits = isinf(self->_constrainedSize.height) ? YES : NO; - - CGSize longestWordSize = CGSizeZero; - if (longestWordFits == NO) { - NSRange longestWordRange = [str rangeOfString:longestWordNeedingResize]; - NSAttributedString *attrString = [textStorage attributedSubstringFromRange:longestWordRange]; - longestWordSize = [attrString boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin context:nil].size; - } - - // we may need to shrink for some reason, so let's iterate through our scale factors to see if we actually need to shrink - // Note: the first scale factor in the array is 1.0 so will make sure that things don't fit without shrinking - for (NSNumber *adjustedScaleObj in scaleFactors) { - if (longestWordFits && maxLinesFits && heightFits) { - break; - } - - adjustedScale = [adjustedScaleObj floatValue]; - - if (longestWordFits == NO) { - // we need to check the longest word to make sure it fits - longestWordFits = std::ceil((longestWordSize.width * adjustedScale) <= self->_constrainedSize.width); - } - - // if the longest word fits, go ahead and check max line and height. If it didn't fit continue to the next scale factor - if (longestWordFits == YES) { - - // scale our string by the current scale factor - NSMutableAttributedString *scaledString = [[NSMutableAttributedString alloc] initWithAttributedString:textStorage]; - [[self class] adjustFontSizeForAttributeString:scaledString withScaleFactor:adjustedScale]; - - // check to see if this scaled string fit in the max lines - if (maxLinesFits == NO) { - maxLinesFits = ([self lineCountForString:scaledString] <= self->_attributes.maximumNumberOfLines); - } - - // if max lines still doesn't fit, continue without checking that we fit in the constrained height - if (maxLinesFits == YES && heightFits == NO) { - // max lines fit so make sure that we fit in the constrained height. - CGSize stringSize = [self boundingBoxForString:scaledString]; - heightFits = (stringSize.height <= self->_constrainedSize.height); - } - } - } - - }]; - _measured = YES; - _scaleFactor = adjustedScale; - return _scaleFactor; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitRenderer+Positioning.h b/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitRenderer+Positioning.h deleted file mode 100644 index c887282568..0000000000 --- a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitRenderer+Positioning.h +++ /dev/null @@ -1,106 +0,0 @@ -// -// ASTextKitRenderer+Positioning.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_ENABLE_TEXTNODE - -typedef void (^as_text_component_index_block_t)(NSUInteger characterIndex, - CGRect glyphBoundingRect, - BOOL *stop); - -/** - Measure options are used to specify which type of line height measurement to use. - - ASTextNodeRendererMeasureOptionLineHeight is faster and will give the height from the baseline to the next line. - - ASTextNodeRendererMeasureOptionCapHeight is a more nuanced measure of the glyphs in the given range that attempts to - produce a visually balanced rectangle above and below the glyphs to produce nice looking text highlights. - - ASTextNodeRendererMeasureOptionBlock uses the cap height option to generate each glyph index, but combines all but the - first and last line rect into a single block. Looks nice for multiline selection. - */ -typedef NS_ENUM(NSUInteger, ASTextKitRendererMeasureOption) { - ASTextKitRendererMeasureOptionLineHeight, - ASTextKitRendererMeasureOptionCapHeight, - ASTextKitRendererMeasureOptionBlock -}; - -@interface ASTextKitRenderer (Positioning) - -/** - Returns the bounding rect for the given character range. - - @param textRange The character range for which the bounding rect will be computed. Should be within the range of the - attributedString of this renderer. - - @discussion In the external, shadowed coordinate space. - */ -- (CGRect)frameForTextRange:(NSRange)textRange; - -/** - Returns an array of rects representing the lines in the given character range - - @param textRange The character range for which the rects will be computed. Should be within the range of the - attributedString of this renderer. - @param measureOption The measure option to use for construction of the rects. See ASTextKitRendererMeasureOption - docs for usage. - - @discussion This method is useful for providing highlighting text. Returned rects are in the coordinate space of the - renderer. - - Triggers initialization of textkit components, truncation, and sizing. - */ -- (NSArray *)rectsForTextRange:(NSRange)textRange - measureOption:(ASTextKitRendererMeasureOption)measureOption; - -/** - Enumerate the text character indexes at a position within the coordinate space of the renderer. - - @param position The point in the shadowed coordinate space at which text indexes will be enumerated. - @param block The block that will be executed for each index identified that may correspond to the given position. The - block is given the character index that corresponds to the glyph at each index in question, as well as the bounding - rect for that glyph. - - @discussion Glyph location based on a touch point is not an exact science because user touches are not well-represented - by a simple point, especially in the context of link-heavy text. So we have this method to make it a bit easier. This - method checks a grid of candidate positions around the touch point you give it, and computes the bounding rect of the - glyph corresponding to the character index given. - - The bounding rect of the glyph can be used to identify the best glyph index that corresponds to your touch. For - instance, comparing centroidal distance from the glyph bounding rect to the touch center is useful for identifying - which link a user actually intended to select. - - Triggers initialization of textkit components, truncation, and sizing. - */ -- (void)enumerateTextIndexesAtPosition:(CGPoint)position - usingBlock:(as_text_component_index_block_t)block; - -/** - Returns the single text index whose glyph's centroid is closest to the given position. - - @param position The point in the shadowed coordinate space that should be checked. - - @discussion This will use the grid enumeration function above, `enumerateTextIndexesAtPosition...`, in order to find - the closest glyph, so it is possible that a glyph could be missed, but ultimately unlikely. - */ -- (NSUInteger)nearestTextIndexAtPosition:(CGPoint)position; - -/** - Returns the trailing rect unused by the renderer in the last rendered line. - - @discussion In the external shadowed coordinate space. - - Triggers initialization of textkit components, truncation, and sizing. - */ -- (CGRect)trailingRect; - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitRenderer+Positioning.mm b/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitRenderer+Positioning.mm deleted file mode 100644 index 9dc770e1d9..0000000000 --- a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitRenderer+Positioning.mm +++ /dev/null @@ -1,386 +0,0 @@ -// -// ASTextKitRenderer+Positioning.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_ENABLE_TEXTNODE - -#import -#import - -#import - -#import -#import - -static const CGFloat ASTextKitRendererGlyphTouchHitSlop = 5.0; -static const CGFloat ASTextKitRendererTextCapHeightPadding = 1.3; - -@implementation ASTextKitRenderer (Tracking) - -- (NSArray *)rectsForTextRange:(NSRange)textRange measureOption:(ASTextKitRendererMeasureOption)measureOption -{ - __block NSArray *textRects = nil; - [self.context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { - textRects = [self unlockedRectsForTextRange:textRange measureOptions:measureOption layoutManager:layoutManager textStorage:textStorage textContainer:textContainer]; - }]; - return textRects; -} - -/** - Helper function that should be called within performBlockWithLockedTextKitComponents: in an already locked state to - prevent a deadlock - */ -- (NSArray *)unlockedRectsForTextRange:(NSRange)textRange measureOptions:(ASTextKitRendererMeasureOption)measureOption layoutManager:(NSLayoutManager *)layoutManager textStorage:(NSTextStorage *)textStorage textContainer:(NSTextContainer *)textContainer -{ - NSRange clampedRange = NSIntersectionRange(textRange, NSMakeRange(0, [textStorage length])); - if (clampedRange.location == NSNotFound || clampedRange.length == 0) { - return @[]; - } - - // Used for block measure option - __block CGRect firstRect = CGRectNull; - __block CGRect lastRect = CGRectNull; - __block CGRect blockRect = CGRectNull; - NSMutableArray *mutableTextRects = [NSMutableArray array]; - - NSString *string = textStorage.string; - - NSRange totalGlyphRange = [layoutManager glyphRangeForCharacterRange:clampedRange actualCharacterRange:NULL]; - - [layoutManager enumerateLineFragmentsForGlyphRange:totalGlyphRange usingBlock:^(CGRect rect, - CGRect usedRect, - NSTextContainer *innerTextContainer, - NSRange glyphRange, - BOOL *stop) { - - CGRect lineRect = CGRectNull; - // If we're empty, don't bother looping through glyphs, use the default. - if (CGRectIsEmpty(usedRect)) { - lineRect = usedRect; - } else { - // TextKit's bounding rect computations are just a touch off, so we actually - // compose the rects by hand from the center of the given TextKit bounds and - // imposing the font attributes returned by the glyph's font. - NSRange lineGlyphRange = NSIntersectionRange(totalGlyphRange, glyphRange); - for (NSUInteger i = lineGlyphRange.location; i < NSMaxRange(lineGlyphRange) && i < string.length; i++) { - // We grab the properly sized rect for the glyph - CGRect properGlyphRect = [self _internalRectForGlyphAtIndex:i - measureOption:measureOption - layoutManager:layoutManager - textContainer:textContainer - textStorage:textStorage]; - - // Don't count empty glyphs towards our line rect. - if (!CGRectIsEmpty(properGlyphRect)) { - lineRect = CGRectIsNull(lineRect) ? properGlyphRect - : CGRectUnion(lineRect, properGlyphRect); - } - } - } - - if (!CGRectIsNull(lineRect)) { - if (measureOption == ASTextKitRendererMeasureOptionBlock) { - // For the block measurement option we store the first & last rect as - // special cases, then merge everything else into a single block rect - if (CGRectIsNull(firstRect)) { - // We don't have a firstRect, so we must be on the first line. - firstRect = lineRect; - } else if(CGRectIsNull(lastRect)) { - // We don't have a lastRect, but we do have a firstRect, so we must - // be on the second line. No need to merge in the blockRect just yet - lastRect = lineRect; - } else if(CGRectIsNull(blockRect)) { - // We have both a first and last rect, so we must be on the third line - // we don't have any blockRect to merge it into, so we just set it - // directly. - blockRect = lastRect; - lastRect = lineRect; - } else { - // Everything is already set, so we just merge this line into the - // block. - blockRect = CGRectUnion(blockRect, lastRect); - lastRect = lineRect; - } - } else { - // If the block option isn't being used then each line is being treated - // individually. - [mutableTextRects addObject:[NSValue valueWithCGRect:[self.shadower offsetRectWithInternalRect:lineRect]]]; - } - } - }]; - - if (measureOption == ASTextKitRendererMeasureOptionBlock) { - // Block measure option is handled differently with just 3 vars for the entire range. - if (!CGRectIsNull(firstRect)) { - if (!CGRectIsNull(blockRect)) { - CGFloat rightEdge = MAX(CGRectGetMaxX(blockRect), CGRectGetMaxX(lastRect)); - if (rightEdge > CGRectGetMaxX(firstRect)) { - // Force the right side of the first rect to properly align with the - // right side of the rightmost of the block and last rect - firstRect.size.width += rightEdge - CGRectGetMaxX(firstRect); - } - - // Force the left side of the block rect to properly align with the - // left side of the leftmost of the first and last rect - blockRect.origin.x = MIN(CGRectGetMinX(firstRect), CGRectGetMinX(lastRect)); - // Force the right side of the block rect to properly align with the - // right side of the rightmost of the first and last rect - blockRect.size.width += MAX(CGRectGetMaxX(firstRect), CGRectGetMaxX(lastRect)) - CGRectGetMaxX(blockRect); - } - if (!CGRectIsNull(lastRect)) { - // Force the left edge of the last rect to properly align with the - // left side of the leftmost of the first and block rect, if necessary. - CGFloat leftEdge = MIN(CGRectGetMinX(blockRect), CGRectGetMinX(firstRect)); - CGFloat lastRectNudgeAmount = MAX(CGRectGetMinX(lastRect) - leftEdge, 0); - lastRect.origin.x = MIN(leftEdge, CGRectGetMinX(lastRect)); - lastRect.size.width += lastRectNudgeAmount; - } - - [mutableTextRects addObject:[NSValue valueWithCGRect:[self.shadower offsetRectWithInternalRect:firstRect]]]; - } - if (!CGRectIsNull(blockRect)) { - [mutableTextRects addObject:[NSValue valueWithCGRect:[self.shadower offsetRectWithInternalRect:blockRect]]]; - } - if (!CGRectIsNull(lastRect)) { - [mutableTextRects addObject:[NSValue valueWithCGRect:[self.shadower offsetRectWithInternalRect:lastRect]]]; - } - } - - return [mutableTextRects copy]; -} - -- (NSUInteger)nearestTextIndexAtPosition:(CGPoint)position -{ - // Check in a 9-point region around the actual touch point so we make sure - // we get the best attribute for the touch. - __block CGFloat minimumGlyphDistance = CGFLOAT_MAX; - __block NSUInteger minimumGlyphCharacterIndex = NSNotFound; - - [self enumerateTextIndexesAtPosition:position usingBlock:^(NSUInteger characterIndex, CGRect glyphBoundingRect, BOOL *stop) { - CGPoint glyphLocation = CGPointMake(CGRectGetMidX(glyphBoundingRect), CGRectGetMidY(glyphBoundingRect)); - CGFloat currentDistance = std::sqrt(std::pow(position.x - glyphLocation.x, 2.f) + std::pow(position.y - glyphLocation.y, 2.f)); - if (currentDistance < minimumGlyphDistance) { - minimumGlyphDistance = currentDistance; - minimumGlyphCharacterIndex = characterIndex; - } - }]; - return minimumGlyphCharacterIndex; -} - -/** - Measured from the internal coordinate space of the context, not accounting for shadow offsets. Actually uses CoreText - as an approximation to work around problems in TextKit's glyph sizing. - */ -- (CGRect)_internalRectForGlyphAtIndex:(NSUInteger)glyphIndex - measureOption:(ASTextKitRendererMeasureOption)measureOption - layoutManager:(NSLayoutManager *)layoutManager - textContainer:(NSTextContainer *)textContainer - textStorage:(NSTextStorage *)textStorage -{ - NSUInteger charIndex = [layoutManager characterIndexForGlyphAtIndex:glyphIndex]; - CGGlyph glyph = [layoutManager glyphAtIndex:glyphIndex]; - CTFontRef font = (__bridge_retained CTFontRef)[textStorage attribute:NSFontAttributeName - atIndex:charIndex - effectiveRange:NULL]; - if (font == nil) { - font = (__bridge_retained CTFontRef)[UIFont systemFontOfSize:12.0]; - } - - // Glyph Advance - // +-------------------------+ - // | | - // | | - // +------------------------+--|-------------------------|--+-----------+-----+ What TextKit returns sometimes - // | | | XXXXXXXXXXX + | | | (approx. correct height, but - // | ---------|--+---------+ XXX XXXX +|-----------|-----| sometimes inaccurate bounding - // | | | XXX XXXXX| | | widths) - // | | | XX XX | | | - // | | | XX | | | - // | | | XXX | | | - // | | | XX | | | - // | | | XXXXXXXXXXX | | | - // | Cap Height->| | XX | | | - // | | | XX | Ascent-->| | - // | | | XX | | | - // | | | XX | | | - // | | | X | | | - // | | | X | | | - // | | | X | | | - // | | | XX | | | - // | | | X | | | - // | ---------|-------+ X +-------------------------------------| - // | | XX | | - // | | X | | - // | | XX Descent------>| | - // | | XXXXXX | | - // | | XXX | | - // +------------------------+-------------------------------------------------+ - // | - // +--+Actual bounding box - - CGRect glyphRect = [layoutManager boundingRectForGlyphRange:NSMakeRange(glyphIndex, 1) - inTextContainer:textContainer]; - - // If it is a NSTextAttachment, we don't have the matched glyph and use width of glyphRect instead of advance. - CGFloat advance = (glyph == kCGFontIndexInvalid) ? glyphRect.size.width : CTFontGetAdvancesForGlyphs(font, kCTFontOrientationHorizontal, &glyph, NULL, 1); - - // We treat the center of the glyph's bounding box as the center of our new rect - CGPoint glyphCenter = CGPointMake(CGRectGetMidX(glyphRect), CGRectGetMidY(glyphRect)); - - CGRect properGlyphRect; - if (measureOption == ASTextKitRendererMeasureOptionCapHeight - || measureOption == ASTextKitRendererMeasureOptionBlock) { - CGFloat ascent = CTFontGetAscent(font); - CGFloat descent = CTFontGetDescent(font); - CGFloat capHeight = CTFontGetCapHeight(font); - CGFloat leading = CTFontGetLeading(font); - CGFloat glyphHeight = ascent + descent; - - // For visual balance, we add the cap height padding above the cap, and - // below the baseline, we scale by the descent so it grows with the size of - // the text. - CGFloat topPadding = ASTextKitRendererTextCapHeightPadding * descent; - CGFloat bottomPadding = topPadding; - - properGlyphRect = CGRectMake(glyphCenter.x - advance * 0.5, - glyphCenter.y - glyphHeight * 0.5 + (ascent - capHeight) - topPadding + leading, - advance, - capHeight + topPadding + bottomPadding); - } else { - // We are just measuring the line heights here, so we can use the - // heights used by TextKit, which tend to be pretty good. - properGlyphRect = CGRectMake(glyphCenter.x - advance * 0.5, - glyphRect.origin.y, - advance, - glyphRect.size.height); - } - - CFRelease(font); - - return properGlyphRect; -} - -- (void)enumerateTextIndexesAtPosition:(CGPoint)externalPosition usingBlock:(as_text_component_index_block_t)block -{ - // This method is a little complex because it has to call out to client code from inside an enumeration that needs - // to achieve a lock on the textkit components. It cannot call out to client code from within that lock so we just - // perform the textkit-locked ops inside the locked context. - ASTextKitContext *lockingContext = self.context; - CGPoint internalPosition = [self.shadower offsetPointWithExternalPoint:externalPosition]; - __block BOOL invalidPosition = NO; - [lockingContext performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { - invalidPosition = internalPosition.x > textContainer.size.width - || internalPosition.y > textContainer.size.height - || block == NULL; - }]; - if (invalidPosition) { - // Short circuit if the position is outside the size of this renderer, or if the block is null. - return; - } - - // We break it up into a 44pt box for the touch, and find the closest link attribute-containing glyph to the center of - // the touch. - CGFloat squareSide = 44.f; - // Should be odd if you want to test the center of the touch. - NSInteger pointsOnASide = 3; - - // The distance between any 2 of the adjacent points - CGFloat pointSeparation = squareSide / pointsOnASide; - // These are for tracking which point we're on. We start with -pointsOnASide/2 and go to pointsOnASide/2. So if - // pointsOnASide=3, we go from -1 to 1. - NSInteger endIndex = pointsOnASide / 2; - NSInteger startIndex = -endIndex; - - BOOL stop = NO; - for (NSInteger i = startIndex; i <= endIndex && !stop; i++) { - for (NSInteger j = startIndex; j <= endIndex && !stop; j++) { - CGPoint currentPoint = CGPointMake(internalPosition.x + i * pointSeparation, - internalPosition.y + j * pointSeparation); - - __block NSUInteger characterIndex = NSNotFound; - __block BOOL isValidGlyph = NO; - __block CGRect glyphRect = CGRectNull; - - [lockingContext performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { - // We ask the layout manager for the proper glyph at the touch point - NSUInteger glyphIndex = [layoutManager glyphIndexForPoint:currentPoint - inTextContainer:textContainer]; - - // If it's an invalid glyph, quit. - - [layoutManager glyphAtIndex:glyphIndex isValidIndex:&isValidGlyph]; - if (!isValidGlyph) { - return; - } - - characterIndex = [layoutManager characterIndexForGlyphAtIndex:glyphIndex]; - - glyphRect = [self _internalRectForGlyphAtIndex:glyphIndex - measureOption:ASTextKitRendererMeasureOptionLineHeight - layoutManager:layoutManager - textContainer:textContainer - textStorage:textStorage]; - }]; - - // Sometimes TextKit plays jokes on us and returns glyphs that really aren't close to the point in question. - // Silly TextKit... - if (!isValidGlyph || !CGRectContainsPoint(CGRectInset(glyphRect, -ASTextKitRendererGlyphTouchHitSlop, -ASTextKitRendererGlyphTouchHitSlop), currentPoint)) { - continue; - } - - block(characterIndex, [self.shadower offsetRectWithInternalRect:glyphRect], &stop); - } - } -} - -- (CGRect)trailingRect -{ - __block CGRect trailingRect = CGRectNull; - [self.context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { - CGSize calculatedSize = textContainer.size; - // If have an empty string, then our whole bounds constitute trailing space. - if ([textStorage length] == 0) { - trailingRect = CGRectMake(0, 0, calculatedSize.width, calculatedSize.height); - return; - } - - // Take everything after our final character as trailing space. - NSRange textRange = NSMakeRange([textStorage length] - 1, 1); - NSArray *finalRects = [self unlockedRectsForTextRange:textRange measureOptions:ASTextKitRendererMeasureOptionLineHeight layoutManager:layoutManager textStorage:textStorage textContainer:textContainer]; - CGRect finalGlyphRect = [[finalRects lastObject] CGRectValue]; - CGPoint origin = CGPointMake(CGRectGetMaxX(finalGlyphRect), CGRectGetMinY(finalGlyphRect)); - CGSize size = CGSizeMake(calculatedSize.width - origin.x, calculatedSize.height - origin.y); - trailingRect = (CGRect){origin, size}; - }]; - return trailingRect; -} - -- (CGRect)frameForTextRange:(NSRange)textRange -{ - __block CGRect textRect = CGRectNull; - [self.context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { - // Bail on invalid range. - if (NSMaxRange(textRange) > [textStorage length]) { - ASDisplayNodeCFailAssert(@"Invalid range"); - return; - } - - // Force glyph generation and layout. - [layoutManager ensureLayoutForTextContainer:textContainer]; - - NSRange glyphRange = [layoutManager glyphRangeForCharacterRange:textRange actualCharacterRange:NULL]; - textRect = [layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer]; - }]; - return textRect; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitRenderer+TextChecking.h b/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitRenderer+TextChecking.h deleted file mode 100644 index d4ba74fd32..0000000000 --- a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitRenderer+TextChecking.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// ASTextKitRenderer+TextChecking.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_ENABLE_TEXTNODE - -/** - Application extensions to NSTextCheckingType. We're allowed to do this (see NSTextCheckingAllCustomTypes). - */ -static uint64_t const ASTextKitTextCheckingTypeEntity = 1ULL << 33; -static uint64_t const ASTextKitTextCheckingTypeTruncation = 1ULL << 34; - -@class ASTextKitEntityAttribute; - -@interface ASTextKitTextCheckingResult : NSTextCheckingResult -@property (nonatomic, readonly) ASTextKitEntityAttribute *entityAttribute; -@end - -@interface ASTextKitRenderer (TextChecking) - -- (NSTextCheckingResult *)textCheckingResultAtPoint:(CGPoint)point; - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitRenderer+TextChecking.mm b/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitRenderer+TextChecking.mm deleted file mode 100644 index e556393572..0000000000 --- a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitRenderer+TextChecking.mm +++ /dev/null @@ -1,104 +0,0 @@ -// -// ASTextKitRenderer+TextChecking.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_ENABLE_TEXTNODE - -#import -#import -#import - -@implementation ASTextKitTextCheckingResult - -{ - // Be explicit about the fact that we are overriding the super class' implementation of -range and -resultType - // and substituting our own custom values. (We could use @synthesize to make these ivars, but our linter correctly - // complains; it's weird to use @synthesize for properties that are redeclared on top of an original declaration in - // the superclass. We only do it here because NSTextCheckingResult doesn't expose an initializer, which is silly.) - NSRange _rangeOverride; - NSTextCheckingType _resultTypeOverride; -} - -- (instancetype)initWithType:(NSTextCheckingType)type - entityAttribute:(ASTextKitEntityAttribute *)entityAttribute - range:(NSRange)range -{ - if ((self = [super init])) { - _resultTypeOverride = type; - _rangeOverride = range; - _entityAttribute = entityAttribute; - } - return self; -} - -- (NSTextCheckingType)resultType -{ - return _resultTypeOverride; -} - -- (NSRange)range -{ - return _rangeOverride; -} - -@end - -@implementation ASTextKitRenderer (TextChecking) - -- (NSTextCheckingResult *)textCheckingResultAtPoint:(CGPoint)point -{ - __block NSTextCheckingResult *result = nil; - NSAttributedString *attributedString = self.attributes.attributedString; - NSAttributedString *truncationAttributedString = self.attributes.truncationAttributedString; - - // get the index of the last character, so we can handle text in the truncation token - __block NSRange truncationTokenRange = { NSNotFound, 0 }; - - [truncationAttributedString enumerateAttribute:ASTextKitTruncationAttributeName inRange:NSMakeRange(0, truncationAttributedString.length) - options:0 - usingBlock:^(id value, NSRange range, BOOL *stop) { - if (value != nil && range.length > 0) { - truncationTokenRange = range; - } - }]; - - if (truncationTokenRange.location == NSNotFound) { - // The truncation string didn't specify a substring which should be highlighted, so we just highlight it all - truncationTokenRange = { 0, truncationAttributedString.length }; - } - - NSRange visibleRange = self.truncater.firstVisibleRange; - truncationTokenRange.location += NSMaxRange(visibleRange); - - __block CGFloat minDistance = CGFLOAT_MAX; - [self enumerateTextIndexesAtPosition:point usingBlock:^(NSUInteger index, CGRect glyphBoundingRect, BOOL *stop){ - if (index >= truncationTokenRange.location) { - result = [[ASTextKitTextCheckingResult alloc] initWithType:ASTextKitTextCheckingTypeTruncation - entityAttribute:nil - range:truncationTokenRange]; - } else { - NSRange range; - NSDictionary *attributes = [attributedString attributesAtIndex:index effectiveRange:&range]; - ASTextKitEntityAttribute *entityAttribute = attributes[ASTextKitEntityAttributeName]; - CGFloat distance = hypot(CGRectGetMidX(glyphBoundingRect) - point.x, CGRectGetMidY(glyphBoundingRect) - point.y); - if (entityAttribute && distance < minDistance) { - result = [[ASTextKitTextCheckingResult alloc] initWithType:ASTextKitTextCheckingTypeEntity - entityAttribute:entityAttribute - range:range]; - minDistance = distance; - } - } - }]; - return result; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitRenderer.h b/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitRenderer.h deleted file mode 100644 index 0f0cc00e0a..0000000000 --- a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitRenderer.h +++ /dev/null @@ -1,108 +0,0 @@ -// -// ASTextKitRenderer.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -#if AS_ENABLE_TEXTNODE - -#import - -#import - -@class ASTextKitContext; -@class ASTextKitShadower; -@class ASTextKitFontSizeAdjuster; -@protocol ASTextKitTruncating; - -/** - ASTextKitRenderer is a modular object that is responsible for laying out and drawing text. - - A renderer will hold onto the TextKit layouts for the given attributes after initialization. This may constitute a - large amount of memory for large enough applications, so care must be taken when keeping many of these around in-memory - at once. - - This object is designed to be modular and simple. All complex maintenance of state should occur in sub-objects or be - derived via pure functions or categories. No touch-related handling belongs in this class. - - ALL sizing and layout information from this class is in the external coordinate space of the TextKit components. This - is an important distinction because all internal sizing and layout operations are carried out within the shadowed - coordinate space. Padding will be added for you in order to ensure clipping does not occur, and additional information - on this transform is available via the shadower should you need it. - */ -@interface ASTextKitRenderer : NSObject - -/** - Designated Initializer - @discussion Sizing will occur as a result of initialization, so be careful when/where you use this. - */ -- (instancetype)initWithTextKitAttributes:(const ASTextKitAttributes &)textComponentAttributes - constrainedSize:(const CGSize)constrainedSize; - -@property (nonatomic, readonly) ASTextKitContext *context; - -@property (nonatomic, readonly) id truncater; - -@property (nonatomic, readonly) ASTextKitFontSizeAdjuster *fontSizeAdjuster; - -@property (nonatomic, readonly) ASTextKitShadower *shadower; - -@property (nonatomic, readonly) ASTextKitAttributes attributes; - -@property (nonatomic, readonly) CGSize constrainedSize; - -@property (nonatomic, readonly) CGFloat currentScaleFactor; - -#pragma mark - Drawing -/** - Draw the renderer's text content into the bounds provided. - - @param bounds The rect in which to draw the contents of the renderer. - */ -- (void)drawInContext:(CGContextRef)context bounds:(CGRect)bounds; - -#pragma mark - Layout - -/** - Returns the computed size of the renderer given the constrained size and other parameters in the initializer. - */ -- (CGSize)size; - -#pragma mark - Text Ranges - -/** - The character range from the original attributedString that is displayed by the renderer given the parameters in the - initializer. - */ -@property (nonatomic, readonly) std::vector visibleRanges; - -/** - The number of lines shown in the string. - */ -- (NSUInteger)lineCount; - -/** - Whether or not the text is truncated. - */ -- (BOOL)isTruncated; - -@end - -@interface ASTextKitRenderer (ASTextKitRendererConvenience) - -/** - Returns the first visible range or an NSRange with location of NSNotFound and size of 0 if no first visible - range exists - */ -@property (nonatomic, readonly) NSRange firstVisibleRange; - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitRenderer.mm b/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitRenderer.mm deleted file mode 100644 index b724d70747..0000000000 --- a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitRenderer.mm +++ /dev/null @@ -1,295 +0,0 @@ -// -// ASTextKitRenderer.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_ENABLE_TEXTNODE - -#import - -#import -#import -#import -#import -#import -#import - -//#define LOG(...) NSLog(__VA_ARGS__) -#define LOG(...) - -static NSCharacterSet *_defaultAvoidTruncationCharacterSet() -{ - static NSCharacterSet *truncationCharacterSet; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSMutableCharacterSet *mutableCharacterSet = [[NSMutableCharacterSet alloc] init]; - [mutableCharacterSet formUnionWithCharacterSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; - [mutableCharacterSet addCharactersInString:@".,!?:;"]; - truncationCharacterSet = mutableCharacterSet; - }); - return truncationCharacterSet; -} - -@implementation ASTextKitRenderer { - CGSize _calculatedSize; -} -@synthesize attributes = _attributes, context = _context, shadower = _shadower, truncater = _truncater, fontSizeAdjuster = _fontSizeAdjuster; - -#pragma mark - Initialization - -- (instancetype)initWithTextKitAttributes:(const ASTextKitAttributes &)attributes - constrainedSize:(const CGSize)constrainedSize -{ - if (self = [super init]) { - _constrainedSize = constrainedSize; - _attributes = attributes; - _currentScaleFactor = 1; - - // As the renderer should be thread safe, create all subcomponents in the initialization method - _shadower = [ASTextKitShadower shadowerWithShadowOffset:attributes.shadowOffset - shadowColor:attributes.shadowColor - shadowOpacity:attributes.shadowOpacity - shadowRadius:attributes.shadowRadius]; - - // We must inset the constrained size by the size of the shadower. - CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:_constrainedSize]; - - _context = [[ASTextKitContext alloc] initWithAttributedString:attributes.attributedString - lineBreakMode:attributes.lineBreakMode - maximumNumberOfLines:attributes.maximumNumberOfLines - exclusionPaths:attributes.exclusionPaths - constrainedSize:shadowConstrainedSize]; - - NSCharacterSet *avoidTailTruncationSet = attributes.avoidTailTruncationSet ?: _defaultAvoidTruncationCharacterSet(); - _truncater = [[ASTextKitTailTruncater alloc] initWithContext:[self context] - truncationAttributedString:attributes.truncationAttributedString - avoidTailTruncationSet:avoidTailTruncationSet]; - - ASTextKitAttributes attributes = _attributes; - // We must inset the constrained size by the size of the shadower. - _fontSizeAdjuster = [[ASTextKitFontSizeAdjuster alloc] initWithContext:[self context] - constrainedSize:shadowConstrainedSize - textKitAttributes:attributes]; - - // Calcualate size immediately - [self _calculateSize]; - } - return self; -} - -- (NSStringDrawingContext *)stringDrawingContext -{ - // String drawing contexts are not safe to use from more than one thread. - // i.e. if they are created on one thread, it is unsafe to use them on another. - // Therefore we always need to create a new one. - // - // http://web.archive.org/web/20140703122636/https://developer.apple.com/library/ios/documentation/uikit/reference/NSAttributedString_UIKit_Additions/Reference/Reference.html - NSStringDrawingContext *stringDrawingContext = [[NSStringDrawingContext alloc] init]; - - if (isinf(_constrainedSize.width) == NO && _attributes.maximumNumberOfLines > 0) { - ASDisplayNodeAssert(_attributes.maximumNumberOfLines != 1, @"Max line count 1 is not supported in fast-path."); - [stringDrawingContext setValue:@(_attributes.maximumNumberOfLines) forKey:@"maximumNumberOfLines"]; - } - return stringDrawingContext; -} - -#pragma mark - Sizing - -- (CGSize)size -{ - return _calculatedSize; -} - -- (void)_calculateSize -{ - // if we have no scale factors or an unconstrained width, there is no reason to try to adjust the font size - if (isinf(_constrainedSize.width) == NO && [_attributes.pointSizeScaleFactors count] > 0) { - _currentScaleFactor = [[self fontSizeAdjuster] scaleFactor]; - } - - const CGRect constrainedRect = {CGPointZero, _constrainedSize}; - - // If we do not scale, do exclusion, or do custom truncation, we should just use NSAttributedString drawing for a fast-path. - if (self.canUseFastPath) { - CGRect rect = [_attributes.attributedString boundingRectWithSize:_constrainedSize options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine context:self.stringDrawingContext]; - // Intersect with constrained rect, in case text kit goes out-of-bounds. - rect = CGRectIntersection(rect, constrainedRect); - _calculatedSize = [self.shadower outsetSizeWithInsetSize:rect.size]; - return; - } - - BOOL isScaled = [self isScaled]; - __block NSTextStorage *scaledTextStorage = nil; - if (isScaled) { - // apply the string scale before truncating or else we may truncate the string after we've done the work to shrink it. - [[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { - NSMutableAttributedString *scaledString = [[NSMutableAttributedString alloc] initWithAttributedString:textStorage]; - [ASTextKitFontSizeAdjuster adjustFontSizeForAttributeString:scaledString withScaleFactor:self->_currentScaleFactor]; - scaledTextStorage = [[NSTextStorage alloc] initWithAttributedString:scaledString]; - - [textStorage removeLayoutManager:layoutManager]; - [scaledTextStorage addLayoutManager:layoutManager]; - }]; - } - - [[self truncater] truncate]; - - __block CGRect boundingRect; - - // Force glyph generation and layout, which may not have happened yet (and isn't triggered by - // -usedRectForTextContainer:). - [[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { - [layoutManager ensureLayoutForTextContainer:textContainer]; - boundingRect = [layoutManager usedRectForTextContainer:textContainer]; - if (isScaled) { - // put the non-scaled version back - [scaledTextStorage removeLayoutManager:layoutManager]; - [textStorage addLayoutManager:layoutManager]; - } - }]; - - // TextKit often returns incorrect glyph bounding rects in the horizontal direction, so we clip to our bounding rect - // to make sure our width calculations aren't being offset by glyphs going beyond the constrained rect. - boundingRect = CGRectIntersection(boundingRect, constrainedRect); - _calculatedSize = [_shadower outsetSizeWithInsetSize:boundingRect.size]; -} - -- (BOOL)isScaled -{ - return (_currentScaleFactor > 0 && _currentScaleFactor < 1.0); -} - -- (BOOL)usesCustomTruncation -{ - // NOTE: This code does not correctly handle if they set `…` with different attributes. - return _attributes.avoidTailTruncationSet != nil || [_attributes.truncationAttributedString.string isEqualToString:@"\u2026"] == NO; -} - -- (BOOL)usesExclusionPaths -{ - return _attributes.exclusionPaths.count > 0; -} - -- (BOOL)canUseFastPath -{ - return NO; -// Fast path is temporarily disabled, because it's crashing in production. -// NOTE: Remember to re-enable testFastPathTruncation when we re-enable this. -// return self.isScaled == NO -// && self.usesCustomTruncation == NO -// && self.usesExclusionPaths == NO -// // NSAttributedString drawing methods ignore usesLineFragmentOrigin if max line count 1, -// // rendering them useless: -// && (_attributes.maximumNumberOfLines != 1 || isinf(_constrainedSize.width)); -} - -#pragma mark - Drawing - -- (void)drawInContext:(CGContextRef)context bounds:(CGRect)bounds; -{ - // We add an assertion so we can track the rare conditions where a graphics context is not present - ASDisplayNodeAssertNotNil(context, @"This is no good without a context."); - - bounds = CGRectIntersection(bounds, { .size = _constrainedSize }); - CGRect shadowInsetBounds = [[self shadower] insetRectWithConstrainedRect:bounds]; - - CGContextSaveGState(context); - [[self shadower] setShadowInContext:context]; - UIGraphicsPushContext(context); - - LOG(@"%@, shadowInsetBounds = %@",self, NSStringFromCGRect(shadowInsetBounds)); - - // If we use default options, we can use NSAttributedString for a - // fast path. - if (self.canUseFastPath) { - CGRect drawingBounds = shadowInsetBounds; - [_attributes.attributedString drawWithRect:drawingBounds options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine context:self.stringDrawingContext]; - } else { - BOOL isScaled = [self isScaled]; - [[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { - - NSTextStorage *scaledTextStorage = nil; - - if (isScaled) { - // if we are going to scale the text, swap out the non-scaled text for the scaled version. - NSMutableAttributedString *scaledString = [[NSMutableAttributedString alloc] initWithAttributedString:textStorage]; - [ASTextKitFontSizeAdjuster adjustFontSizeForAttributeString:scaledString withScaleFactor:self->_currentScaleFactor]; - scaledTextStorage = [[NSTextStorage alloc] initWithAttributedString:scaledString]; - - [textStorage removeLayoutManager:layoutManager]; - [scaledTextStorage addLayoutManager:layoutManager]; - } - - LOG(@"usedRect: %@", NSStringFromCGRect([layoutManager usedRectForTextContainer:textContainer])); - - NSRange glyphRange = [layoutManager glyphRangeForBoundingRect:(CGRect){ .size = textContainer.size } inTextContainer:textContainer]; - LOG(@"boundingRect: %@", NSStringFromCGRect([layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer])); - - [layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin]; - [layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin]; - - if (isScaled) { - // put the non-scaled version back - [scaledTextStorage removeLayoutManager:layoutManager]; - [textStorage addLayoutManager:layoutManager]; - } - }]; - } - - UIGraphicsPopContext(); - CGContextRestoreGState(context); -} - -#pragma mark - String Ranges - -- (NSUInteger)lineCount -{ - __block NSUInteger lineCount = 0; - [[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { - for (NSRange lineRange = { 0, 0 }; NSMaxRange(lineRange) < [layoutManager numberOfGlyphs]; lineCount++) { - [layoutManager lineFragmentRectForGlyphAtIndex:NSMaxRange(lineRange) effectiveRange:&lineRange]; - } - }]; - return lineCount; -} - -- (BOOL)isTruncated -{ - if (self.canUseFastPath) { - CGRect boundedRect = [_attributes.attributedString boundingRectWithSize:CGSizeMake(_constrainedSize.width, CGFLOAT_MAX) - options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine - context:nil]; - return boundedRect.size.height > _constrainedSize.height; - } else { - return self.firstVisibleRange.length < _attributes.attributedString.length; - } -} - -- (std::vector)visibleRanges -{ - return _truncater.visibleRanges; -} - -@end - -@implementation ASTextKitRenderer (ASTextKitRendererConvenience) - -- (NSRange)firstVisibleRange -{ - std::vector visibleRanges = self.visibleRanges; - if (visibleRanges.size() > 0) { - return visibleRanges[0]; - } - - return NSMakeRange(0, 0); -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitShadower.h b/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitShadower.h deleted file mode 100644 index 2f9c604fbc..0000000000 --- a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitShadower.h +++ /dev/null @@ -1,78 +0,0 @@ -// -// ASTextKitShadower.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -#if AS_ENABLE_TEXTNODE - -#import - -/** - * @abstract an immutable class for calculating shadow padding drawing a shadowed background for text - */ -AS_SUBCLASSING_RESTRICTED -@interface ASTextKitShadower : NSObject - -+ (ASTextKitShadower *)shadowerWithShadowOffset:(CGSize)shadowOffset - shadowColor:(UIColor *)shadowColor - shadowOpacity:(CGFloat)shadowOpacity - shadowRadius:(CGFloat)shadowRadius; - -/** - * @abstract The offset from the top-left corner at which the shadow starts. - * @discussion A positive width will move the shadow to the right. - * A positive height will move the shadow downwards. - */ -@property (nonatomic, readonly) CGSize shadowOffset; - -//! CGColor in which the shadow is drawn -@property (nonatomic, copy, readonly) UIColor *shadowColor; - -//! Alpha of the shadow -@property (nonatomic, readonly) CGFloat shadowOpacity; - -//! Radius, in pixels -@property (nonatomic, readonly) CGFloat shadowRadius; - -/** - * @abstract The edge insets which represent shadow padding - * @discussion Each edge inset is less than or equal to zero. - * - * Example: - * CGRect boundsWithoutShadowPadding; // Large enough to fit text, not large enough to fit the shadow as well - * UIEdgeInsets shadowPadding = [shadower shadowPadding]; - * CGRect boundsWithShadowPadding = UIEdgeInsetsRect(boundsWithoutShadowPadding, shadowPadding); - */ -- (UIEdgeInsets)shadowPadding; - -- (CGSize)insetSizeWithConstrainedSize:(CGSize)constrainedSize; - -- (CGRect)insetRectWithConstrainedRect:(CGRect)constrainedRect; - -- (CGSize)outsetSizeWithInsetSize:(CGSize)insetSize; - -- (CGRect)outsetRectWithInsetRect:(CGRect)insetRect; - -- (CGRect)offsetRectWithInternalRect:(CGRect)internalRect; - -- (CGPoint)offsetPointWithInternalPoint:(CGPoint)internalPoint; - -- (CGPoint)offsetPointWithExternalPoint:(CGPoint)externalPoint; - -/** - * @abstract draws the shadow for text in the provided CGContext - * @discussion Call within the text node's +drawRect method - */ -- (void)setShadowInContext:(CGContextRef)context; - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitShadower.mm b/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitShadower.mm deleted file mode 100644 index a2f37f7e06..0000000000 --- a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitShadower.mm +++ /dev/null @@ -1,177 +0,0 @@ -// -// ASTextKitShadower.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_ENABLE_TEXTNODE - -#import - -static inline CGSize _insetSize(CGSize size, UIEdgeInsets insets) -{ - size.width -= (insets.left + insets.right); - size.height -= (insets.top + insets.bottom); - return size; -} - -static inline UIEdgeInsets _invertInsets(UIEdgeInsets insets) -{ - return { - .top = -insets.top, - .left = -insets.left, - .bottom = -insets.bottom, - .right = -insets.right - }; -} - -@implementation ASTextKitShadower { - UIEdgeInsets _calculatedShadowPadding; -} - -+ (ASTextKitShadower *)shadowerWithShadowOffset:(CGSize)shadowOffset - shadowColor:(UIColor *)shadowColor - shadowOpacity:(CGFloat)shadowOpacity - shadowRadius:(CGFloat)shadowRadius -{ - /** - * For all cases where no shadow is drawn, we share this singleton shadower to save resources. - */ - static dispatch_once_t onceToken; - static ASTextKitShadower *sharedNonShadower; - dispatch_once(&onceToken, ^{ - sharedNonShadower = [[ASTextKitShadower alloc] initWithShadowOffset:CGSizeZero shadowColor:nil shadowOpacity:0 shadowRadius:0]; - }); - - BOOL hasShadow = shadowOpacity > 0 && (shadowRadius > 0 || CGSizeEqualToSize(shadowOffset, CGSizeZero) == NO) && CGColorGetAlpha(shadowColor.CGColor) > 0; - if (hasShadow == NO) { - return sharedNonShadower; - } else { - return [[ASTextKitShadower alloc] initWithShadowOffset:shadowOffset shadowColor:shadowColor shadowOpacity:shadowOpacity shadowRadius:shadowRadius]; - } -} - -- (instancetype)initWithShadowOffset:(CGSize)shadowOffset - shadowColor:(UIColor *)shadowColor - shadowOpacity:(CGFloat)shadowOpacity - shadowRadius:(CGFloat)shadowRadius -{ - if (self = [super init]) { - _shadowOffset = shadowOffset; - _shadowColor = shadowColor; - _shadowOpacity = shadowOpacity; - _shadowRadius = shadowRadius; - _calculatedShadowPadding = UIEdgeInsetsMake(-INFINITY, -INFINITY, INFINITY, INFINITY); - } - return self; -} - -/* - * This method is duplicated here because it gets called frequently, and we were - * wasting valuable time constructing a state object to ask it. - */ -- (BOOL)_shouldDrawShadow -{ - return _shadowOpacity != 0.0 && (_shadowRadius != 0 || !CGSizeEqualToSize(_shadowOffset, CGSizeZero)) && CGColorGetAlpha(_shadowColor.CGColor) > 0; -} - -- (void)setShadowInContext:(CGContextRef)context -{ - if ([self _shouldDrawShadow]) { - CGColorRef textShadowColor = CGColorRetain(_shadowColor.CGColor); - CGSize textShadowOffset = _shadowOffset; - CGFloat textShadowOpacity = _shadowOpacity; - CGFloat textShadowRadius = _shadowRadius; - - if (textShadowOpacity != 1.0) { - CGFloat inherentAlpha = CGColorGetAlpha(textShadowColor); - - CGColorRef oldTextShadowColor = textShadowColor; - textShadowColor = CGColorCreateCopyWithAlpha(textShadowColor, inherentAlpha * textShadowOpacity); - CGColorRelease(oldTextShadowColor); - } - - CGContextSetShadowWithColor(context, textShadowOffset, textShadowRadius, textShadowColor); - - CGColorRelease(textShadowColor); - } -} - - -- (UIEdgeInsets)shadowPadding -{ - if (_calculatedShadowPadding.top == -INFINITY) { - if (![self _shouldDrawShadow]) { - return UIEdgeInsetsZero; - } - - UIEdgeInsets shadowPadding = UIEdgeInsetsZero; - - // min values are expected to be negative for most typical shadowOffset and - // blurRadius settings: - shadowPadding.top = std::fmin(0.0f, _shadowOffset.height - _shadowRadius); - shadowPadding.left = std::fmin(0.0f, _shadowOffset.width - _shadowRadius); - - shadowPadding.bottom = std::fmin(0.0f, -_shadowOffset.height - _shadowRadius); - shadowPadding.right = std::fmin(0.0f, -_shadowOffset.width - _shadowRadius); - - _calculatedShadowPadding = shadowPadding; - } - - return _calculatedShadowPadding; -} - -- (CGSize)insetSizeWithConstrainedSize:(CGSize)constrainedSize -{ - return _insetSize(constrainedSize, _invertInsets([self shadowPadding])); -} - -- (CGRect)insetRectWithConstrainedRect:(CGRect)constrainedRect -{ - return UIEdgeInsetsInsetRect(constrainedRect, _invertInsets([self shadowPadding])); -} - -- (CGSize)outsetSizeWithInsetSize:(CGSize)insetSize -{ - return _insetSize(insetSize, [self shadowPadding]); -} - -- (CGRect)outsetRectWithInsetRect:(CGRect)insetRect -{ - return UIEdgeInsetsInsetRect(insetRect, [self shadowPadding]); -} - -- (CGRect)offsetRectWithInternalRect:(CGRect)internalRect -{ - return (CGRect){ - .origin = [self offsetPointWithInternalPoint:internalRect.origin], - .size = internalRect.size - }; -} - -- (CGPoint)offsetPointWithInternalPoint:(CGPoint)internalPoint -{ - UIEdgeInsets shadowPadding = [self shadowPadding]; - return (CGPoint){ - internalPoint.x + shadowPadding.left, - internalPoint.y + shadowPadding.top - }; -} - -- (CGPoint)offsetPointWithExternalPoint:(CGPoint)externalPoint -{ - UIEdgeInsets shadowPadding = [self shadowPadding]; - return (CGPoint){ - externalPoint.x - shadowPadding.left, - externalPoint.y - shadowPadding.top - }; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitTailTruncater.h b/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitTailTruncater.h deleted file mode 100644 index 70734d9e2c..0000000000 --- a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitTailTruncater.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// ASTextKitTailTruncater.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -#if AS_ENABLE_TEXTNODE - -AS_SUBCLASSING_RESTRICTED -@interface ASTextKitTailTruncater : NSObject - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitTailTruncater.mm b/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitTailTruncater.mm deleted file mode 100644 index b81e27be05..0000000000 --- a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitTailTruncater.mm +++ /dev/null @@ -1,196 +0,0 @@ -// -// ASTextKitTailTruncater.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_ENABLE_TEXTNODE - -#import - -@implementation ASTextKitTailTruncater -{ - __weak ASTextKitContext *_context; - NSAttributedString *_truncationAttributedString; - NSCharacterSet *_avoidTailTruncationSet; -} -@synthesize visibleRanges = _visibleRanges; - -- (instancetype)initWithContext:(ASTextKitContext *)context - truncationAttributedString:(NSAttributedString *)truncationAttributedString - avoidTailTruncationSet:(NSCharacterSet *)avoidTailTruncationSet -{ - if (self = [super init]) { - _context = context; - _truncationAttributedString = truncationAttributedString; - _avoidTailTruncationSet = avoidTailTruncationSet; - } - return self; -} - -/** - Calculates the intersection of the truncation message within the end of the last line. - */ -- (NSUInteger)_calculateCharacterIndexBeforeTruncationMessage:(NSLayoutManager *)layoutManager - textStorage:(NSTextStorage *)textStorage - textContainer:(NSTextContainer *)textContainer -{ - CGRect constrainedRect = (CGRect){ .size = textContainer.size }; - - NSRange visibleGlyphRange = [layoutManager glyphRangeForBoundingRect:constrainedRect - inTextContainer:textContainer]; - NSInteger lastVisibleGlyphIndex = (NSMaxRange(visibleGlyphRange) - 1); - - if (lastVisibleGlyphIndex < 0) { - return NSNotFound; - } - - CGRect lastLineRect = [layoutManager lineFragmentRectForGlyphAtIndex:lastVisibleGlyphIndex - effectiveRange:NULL]; - CGRect lastLineUsedRect = [layoutManager lineFragmentUsedRectForGlyphAtIndex:lastVisibleGlyphIndex - effectiveRange:NULL]; - NSParagraphStyle *paragraphStyle = [textStorage attributesAtIndex:[layoutManager characterIndexForGlyphAtIndex:lastVisibleGlyphIndex] - effectiveRange:NULL][NSParagraphStyleAttributeName]; - - // We assume LTR so long as the writing direction is not - BOOL rtlWritingDirection = paragraphStyle ? paragraphStyle.baseWritingDirection == NSWritingDirectionRightToLeft : NO; - // We only want to treat the truncation rect as left-aligned in the case that we are right-aligned and our writing - // direction is RTL. - BOOL leftAligned = CGRectGetMinX(lastLineRect) == CGRectGetMinX(lastLineUsedRect) || !rtlWritingDirection; - - // Calculate the bounding rectangle for the truncation message - ASTextKitContext *truncationContext = [[ASTextKitContext alloc] initWithAttributedString:_truncationAttributedString - lineBreakMode:NSLineBreakByWordWrapping - maximumNumberOfLines:1 - exclusionPaths:nil - constrainedSize:constrainedRect.size]; - __block CGRect truncationUsedRect; - - [truncationContext performBlockWithLockedTextKitComponents:^(NSLayoutManager *truncationLayoutManager, NSTextStorage *truncationTextStorage, NSTextContainer *truncationTextContainer) { - // Size the truncation message - [truncationLayoutManager ensureLayoutForTextContainer:truncationTextContainer]; - NSRange truncationGlyphRange = [truncationLayoutManager glyphRangeForTextContainer:truncationTextContainer]; - truncationUsedRect = [truncationLayoutManager boundingRectForGlyphRange:truncationGlyphRange - inTextContainer:truncationTextContainer]; - }]; - CGFloat truncationOriginX = (leftAligned ? - CGRectGetMaxX(constrainedRect) - truncationUsedRect.size.width : - CGRectGetMinX(constrainedRect)); - CGRect translatedTruncationRect = CGRectMake(truncationOriginX, - CGRectGetMinY(lastLineRect), - truncationUsedRect.size.width, - truncationUsedRect.size.height); - - // Determine which glyph is the first to be clipped / overlaps the truncation message. - CGFloat truncationMessageX = (leftAligned ? - CGRectGetMinX(translatedTruncationRect) : - CGRectGetMaxX(translatedTruncationRect)); - CGPoint beginningOfTruncationMessage = CGPointMake(truncationMessageX, - CGRectGetMidY(translatedTruncationRect)); - NSUInteger firstClippedGlyphIndex = [layoutManager glyphIndexForPoint:beginningOfTruncationMessage - inTextContainer:textContainer - fractionOfDistanceThroughGlyph:NULL]; - // If it didn't intersect with any text then it should just return the last visible character index, since the - // truncation rect can fully fit on the line without clipping any other text. - if (firstClippedGlyphIndex == NSNotFound) { - return [layoutManager characterIndexForGlyphAtIndex:lastVisibleGlyphIndex]; - } - NSUInteger firstCharacterIndexToReplace = [layoutManager characterIndexForGlyphAtIndex:firstClippedGlyphIndex]; - - // Break on word boundaries - return [self _findTruncationInsertionPointAtOrBeforeCharacterIndex:firstCharacterIndexToReplace - layoutManager:layoutManager - textStorage:textStorage]; -} - -/** - Finds the first whitespace at or before the character index do we don't truncate in the middle of words - If there are multiple whitespaces together (say a space and a newline), this will backtrack to the first one - */ -- (NSUInteger)_findTruncationInsertionPointAtOrBeforeCharacterIndex:(NSUInteger)firstCharacterIndexToReplace - layoutManager:(NSLayoutManager *)layoutManager - textStorage:(NSTextStorage *)textStorage -{ - // Don't attempt to truncate beyond the end of the string - if (firstCharacterIndexToReplace >= textStorage.length) { - return 0; - } - - // Find the glyph range of the line fragment containing the first character to replace. - NSRange lineGlyphRange; - [layoutManager lineFragmentRectForGlyphAtIndex:[layoutManager glyphIndexForCharacterAtIndex:firstCharacterIndexToReplace] - effectiveRange:&lineGlyphRange]; - - // Look for the first whitespace from the end of the line, starting from the truncation point - NSUInteger startingSearchIndex = [layoutManager characterIndexForGlyphAtIndex:lineGlyphRange.location]; - NSUInteger endingSearchIndex = firstCharacterIndexToReplace; - NSRange rangeToSearch = NSMakeRange(startingSearchIndex, (endingSearchIndex - startingSearchIndex)); - - NSRange rangeOfLastVisibleAvoidedChars = { .location = NSNotFound }; - if (_avoidTailTruncationSet) { - rangeOfLastVisibleAvoidedChars = [textStorage.string rangeOfCharacterFromSet:_avoidTailTruncationSet - options:NSBackwardsSearch - range:rangeToSearch]; - } - - // Couldn't find a good place to truncate. Might be because there is no whitespace in the text, or we're dealing - // with a foreign language encoding. Settle for truncating at the original place, which may be mid-word. - if (rangeOfLastVisibleAvoidedChars.location == NSNotFound) { - return firstCharacterIndexToReplace; - } else { - return rangeOfLastVisibleAvoidedChars.location; - } -} - -- (void)truncate -{ - [_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { - NSUInteger originalStringLength = textStorage.length; - - [layoutManager ensureLayoutForTextContainer:textContainer]; - - NSRange visibleGlyphRange = [layoutManager glyphRangeForBoundingRect:{ .size = textContainer.size } - inTextContainer:textContainer]; - NSRange visibleCharacterRange = [layoutManager characterRangeForGlyphRange:visibleGlyphRange - actualGlyphRange:NULL]; - - // Check if text is truncated, and if so apply our truncation string - if (visibleCharacterRange.length < originalStringLength && self->_truncationAttributedString.length > 0) { - NSInteger firstCharacterIndexToReplace = [self _calculateCharacterIndexBeforeTruncationMessage:layoutManager - textStorage:textStorage - textContainer:textContainer]; - if (firstCharacterIndexToReplace == 0 || firstCharacterIndexToReplace == NSNotFound) { - return; - } - - // Update/truncate the visible range of text - visibleCharacterRange = NSMakeRange(0, firstCharacterIndexToReplace); - NSRange truncationReplacementRange = NSMakeRange(firstCharacterIndexToReplace, - textStorage.length - firstCharacterIndexToReplace); - // Replace the end of the visible message with the truncation string - [textStorage replaceCharactersInRange:truncationReplacementRange - withAttributedString:self->_truncationAttributedString]; - } - - self->_visibleRanges = { visibleCharacterRange }; - }]; -} - -- (NSRange)firstVisibleRange -{ - std::vector visibleRanges = _visibleRanges; - if (visibleRanges.size() > 0) { - return visibleRanges[0]; - } - - return NSMakeRange(NSNotFound, 0); -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitTruncating.h b/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitTruncating.h deleted file mode 100644 index 35392fd6b3..0000000000 --- a/submodules/AsyncDisplayKit/Source/TextKit/ASTextKitTruncating.h +++ /dev/null @@ -1,61 +0,0 @@ -// -// ASTextKitTruncating.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -#if AS_ENABLE_TEXTNODE - -#import - -#import - -NS_ASSUME_NONNULL_BEGIN - -@class ASTextKitContext; - -@protocol ASTextKitTruncating - -/** - The character range from the original attributedString that is displayed by the renderer given the parameters in the - initializer. - */ -@property (nonatomic, readonly) std::vector visibleRanges; - -/** - Returns the first visible range or an NSRange with location of NSNotFound and size of 0 if no first visible - range exists - */ -@property (nonatomic, readonly) NSRange firstVisibleRange; - -/** - A truncater object is initialized with the full state of the text. It is a Single Responsibility Object that is - mutative. It configures the state of the TextKit components (layout manager, text container, text storage) to achieve - the intended truncation, then it stores the resulting state for later fetching. - - The truncater may mutate the state of the text storage such that only the drawn string is actually present in the - text storage itself. - - The truncater should not store a strong reference to the context to prevent retain cycles. - */ -- (instancetype)initWithContext:(ASTextKitContext *)context - truncationAttributedString:(NSAttributedString * _Nullable)truncationAttributedString - avoidTailTruncationSet:(NSCharacterSet * _Nullable)avoidTailTruncationSet; - -/** - Actually do the truncation. - */ -- (void)truncate; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/UIImage+ASConvenience.h b/submodules/AsyncDisplayKit/Source/UIImage+ASConvenience.h deleted file mode 100644 index b24c97d1c5..0000000000 --- a/submodules/AsyncDisplayKit/Source/UIImage+ASConvenience.h +++ /dev/null @@ -1,109 +0,0 @@ -// -// UIImage+ASConvenience.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * Dramatically faster version of +[UIImage imageNamed:]. Although it is believed that imageNamed: - * has a cache and is fast, it actually performs expensive asset catalog lookups and is often a - * performance bottleneck (verified on iOS 7 through iOS 10). - * - * Use [UIImage as_imageNamed:] anywhere in your app, even if you aren't using other parts of ASDK. - * Although not the best choice for extremely large assets that are only used once, it is the ideal - * choice for any assets used in tab bars, nav bars, buttons, table or collection cells, etc. - */ - -@interface UIImage (ASDKFastImageNamed) - -/** - * A version of imageNamed that caches results because loading an image is expensive. - * Calling with the same name value will usually return the same object. A UIImage, - * after creation, is immutable and thread-safe so it's fine to share these objects across multiple threads. - * - * @param imageName The name of the image to load - * @return The loaded image or nil - */ -+ (nullable UIImage *)as_imageNamed:(NSString *)imageName NS_RETURNS_RETAINED; - -/** - * A version of imageNamed that caches results because loading an image is expensive. - * Calling with the same name value will usually return the same object. A UIImage, - * after creation, is immutable and thread-safe so it's fine to share these objects across multiple threads. - * - * @param imageName The name of the image to load - * @param traitCollection The traits associated with the intended environment for the image. - * @return The loaded image or nil - */ -+ (nullable UIImage *)as_imageNamed:(NSString *)imageName compatibleWithTraitCollection:(nullable UITraitCollection *)traitCollection NS_RETURNS_RETAINED; - -@end - -/** - * High-performance flat-colored, rounded-corner resizable images - * - * For "Baked-in Opaque" corners, set cornerColor equal to the color behind the rounded image object, - * i.e. the background color. - * For "Baked-in Alpha" corners, set cornerColor = [UIColor clearColor] - * - * See http://asyncdisplaykit.org/docs/corner-rounding.html for an explanation. - */ - -@interface UIImage (ASDKResizableRoundedRects) - -/** - * This generates a flat-color, rounded-corner resizeable image - * - * @param cornerRadius The radius of the rounded-corner - * @param cornerColor The fill color of the corners (For Alpha corners use clearColor) - * @param fillColor The fill color of the rounded-corner image - */ -+ (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius - cornerColor:(nullable UIColor *)cornerColor - fillColor:(UIColor *)fillColor NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -/** - * This generates a flat-color, rounded-corner resizeable image with a border - * - * @param cornerRadius The radius of the rounded-corner - * @param cornerColor The fill color of the corners (For Alpha corners use clearColor) - * @param fillColor The fill color of the rounded-corner image - * @param borderColor The border color. Set to nil for no border. - * @param borderWidth The border width. Dummy value if borderColor = nil. - */ -+ (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius - cornerColor:(UIColor *)cornerColor - fillColor:(UIColor *)fillColor - borderColor:(nullable UIColor *)borderColor - borderWidth:(CGFloat)borderWidth NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -/** - * This generates a flat-color, rounded-corner resizeable image with a border - * - * @param cornerRadius The radius of the rounded-corner - * @param cornerColor The fill color of the corners (For Alpha corners use clearColor) - * @param fillColor The fill color of the rounded-corner image - * @param borderColor The border color. Set to nil for no border. - * @param borderWidth The border width. Dummy value if borderColor = nil. - * @param roundedCorners Select individual or multiple corners to round. Set to UIRectCornerAllCorners to round all 4 corners. - * @param scale The number of pixels per point. Provide 0.0 to use the screen scale. - */ -+ (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius - cornerColor:(nullable UIColor *)cornerColor - fillColor:(UIColor *)fillColor - borderColor:(nullable UIColor *)borderColor - borderWidth:(CGFloat)borderWidth - roundedCorners:(UIRectCorner)roundedCorners - scale:(CGFloat)scale NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/UIImage+ASConvenience.mm b/submodules/AsyncDisplayKit/Source/UIImage+ASConvenience.mm deleted file mode 100644 index 936514cf14..0000000000 --- a/submodules/AsyncDisplayKit/Source/UIImage+ASConvenience.mm +++ /dev/null @@ -1,172 +0,0 @@ -// -// UIImage+ASConvenience.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import -#import - -#pragma mark - ASDKFastImageNamed - -@implementation UIImage (ASDKFastImageNamed) - -UIImage *cachedImageNamed(NSString *imageName, UITraitCollection *traitCollection) NS_RETURNS_RETAINED -{ - static NSCache *imageCache = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - // Because NSCache responds to memory warnings, we do not need an explicit limit. - // all of these objects contain compressed image data and are relatively small - // compared to the backing stores of text and image views. - imageCache = [[NSCache alloc] init]; - }); - - UIImage *image = nil; - if ([imageName length] > 0) { - NSString *imageKey = imageName; - if (traitCollection) { - char imageKeyBuffer[256]; - snprintf(imageKeyBuffer, sizeof(imageKeyBuffer), "%s|%ld|%ld", imageName.UTF8String, (long)traitCollection.horizontalSizeClass, (long)traitCollection.verticalSizeClass); - imageKey = [NSString stringWithUTF8String:imageKeyBuffer]; - } - - image = [imageCache objectForKey:imageKey]; - if (!image) { - image = [UIImage imageNamed:imageName inBundle:nil compatibleWithTraitCollection:traitCollection]; - if (image) { - [imageCache setObject:image forKey:imageKey]; - } - } - } - return image; -} - -+ (UIImage *)as_imageNamed:(NSString *)imageName NS_RETURNS_RETAINED -{ - return cachedImageNamed(imageName, nil); -} - -+ (UIImage *)as_imageNamed:(NSString *)imageName compatibleWithTraitCollection:(UITraitCollection *)traitCollection NS_RETURNS_RETAINED -{ - return cachedImageNamed(imageName, traitCollection); -} - -@end - -#pragma mark - ASDKResizableRoundedRects - -@implementation UIImage (ASDKResizableRoundedRects) - -+ (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius - cornerColor:(UIColor *)cornerColor - fillColor:(UIColor *)fillColor NS_RETURNS_RETAINED -{ - return [self as_resizableRoundedImageWithCornerRadius:cornerRadius - cornerColor:cornerColor - fillColor:fillColor - borderColor:nil - borderWidth:1.0 - roundedCorners:UIRectCornerAllCorners - scale:0.0]; -} - -+ (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius - cornerColor:(UIColor *)cornerColor - fillColor:(UIColor *)fillColor - borderColor:(UIColor *)borderColor - borderWidth:(CGFloat)borderWidth NS_RETURNS_RETAINED -{ - return [self as_resizableRoundedImageWithCornerRadius:cornerRadius - cornerColor:cornerColor - fillColor:fillColor - borderColor:borderColor - borderWidth:borderWidth - roundedCorners:UIRectCornerAllCorners - scale:0.0]; -} - -+ (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius - cornerColor:(UIColor *)cornerColor - fillColor:(UIColor *)fillColor - borderColor:(UIColor *)borderColor - borderWidth:(CGFloat)borderWidth - roundedCorners:(UIRectCorner)roundedCorners - scale:(CGFloat)scale NS_RETURNS_RETAINED -{ - static NSCache *__pathCache = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - __pathCache = [[NSCache alloc] init]; - // UIBezierPath objects are fairly small and these are equally sized. 20 should be plenty for many different parameters. - __pathCache.countLimit = 20; - }); - - // Treat clear background color as no background color - if ([cornerColor isEqual:[UIColor clearColor]]) { - cornerColor = nil; - } - - CGFloat dimension = (cornerRadius * 2) + 1; - CGRect bounds = CGRectMake(0, 0, dimension, dimension); - - typedef struct { - UIRectCorner corners; - CGFloat radius; - } PathKey; - PathKey key = { roundedCorners, cornerRadius }; - NSValue *pathKeyObject = [[NSValue alloc] initWithBytes:&key objCType:@encode(PathKey)]; - - CGSize cornerRadii = CGSizeMake(cornerRadius, cornerRadius); - UIBezierPath *path = [__pathCache objectForKey:pathKeyObject]; - if (path == nil) { - path = [UIBezierPath bezierPathWithRoundedRect:bounds byRoundingCorners:roundedCorners cornerRadii:cornerRadii]; - [__pathCache setObject:path forKey:pathKeyObject]; - } - - // We should probably check if the background color has any alpha component but that - // might be expensive due to needing to check mulitple color spaces. - ASGraphicsBeginImageContextWithOptions(bounds.size, cornerColor != nil, scale); - - BOOL contextIsClean = YES; - if (cornerColor) { - contextIsClean = NO; - [cornerColor setFill]; - // Copy "blend" mode is extra fast because it disregards any value currently in the buffer and overrides directly. - UIRectFillUsingBlendMode(bounds, kCGBlendModeCopy); - } - - BOOL canUseCopy = contextIsClean || (CGColorGetAlpha(fillColor.CGColor) == 1); - [fillColor setFill]; - [path fillWithBlendMode:(canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal) alpha:1]; - - if (borderColor) { - [borderColor setStroke]; - - // Inset border fully inside filled path (not halfway on each side of path) - CGRect strokeRect = CGRectInset(bounds, borderWidth / 2.0, borderWidth / 2.0); - - // It is rarer to have a stroke path, and our cache key only handles rounded rects for the exact-stretchable - // size calculated by cornerRadius, so we won't bother caching this path. Profiling validates this decision. - UIBezierPath *strokePath = [UIBezierPath bezierPathWithRoundedRect:strokeRect - byRoundingCorners:roundedCorners - cornerRadii:cornerRadii]; - [strokePath setLineWidth:borderWidth]; - BOOL canUseCopy = (CGColorGetAlpha(borderColor.CGColor) == 1); - [strokePath strokeWithBlendMode:(canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal) alpha:1]; - } - - UIImage *result = ASGraphicsGetImageAndEndCurrentContext(); - - UIEdgeInsets capInsets = UIEdgeInsetsMake(cornerRadius, cornerRadius, cornerRadius, cornerRadius); - result = [result resizableImageWithCapInsets:capInsets resizingMode:UIImageResizingModeStretch]; - - return result; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/UIResponder+AsyncDisplayKit.mm b/submodules/AsyncDisplayKit/Source/UIResponder+AsyncDisplayKit.mm index 8c3ec24de3..9365fba5e0 100644 --- a/submodules/AsyncDisplayKit/Source/UIResponder+AsyncDisplayKit.mm +++ b/submodules/AsyncDisplayKit/Source/UIResponder+AsyncDisplayKit.mm @@ -7,11 +7,11 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "UIResponder+AsyncDisplayKit.h" +#import #import #import -#import +#import "ASResponderChainEnumerator.h" @implementation UIResponder (AsyncDisplayKit) diff --git a/submodules/AsyncDisplayKit/Source/Details/Transactions/_ASAsyncTransaction.mm b/submodules/AsyncDisplayKit/Source/_ASAsyncTransaction.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/Details/Transactions/_ASAsyncTransaction.mm rename to submodules/AsyncDisplayKit/Source/_ASAsyncTransaction.mm diff --git a/submodules/AsyncDisplayKit/Source/Details/Transactions/_ASAsyncTransactionContainer+Private.h b/submodules/AsyncDisplayKit/Source/_ASAsyncTransactionContainer+Private.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/Details/Transactions/_ASAsyncTransactionContainer+Private.h rename to submodules/AsyncDisplayKit/Source/_ASAsyncTransactionContainer+Private.h diff --git a/submodules/AsyncDisplayKit/Source/Details/Transactions/_ASAsyncTransactionContainer.mm b/submodules/AsyncDisplayKit/Source/_ASAsyncTransactionContainer.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/Details/Transactions/_ASAsyncTransactionContainer.mm rename to submodules/AsyncDisplayKit/Source/_ASAsyncTransactionContainer.mm index ed44231ce2..5bbfd95f15 100644 --- a/submodules/AsyncDisplayKit/Source/Details/Transactions/_ASAsyncTransactionContainer.mm +++ b/submodules/AsyncDisplayKit/Source/_ASAsyncTransactionContainer.mm @@ -8,7 +8,7 @@ // #import -#import +#import "_ASAsyncTransactionContainer+Private.h" #import #import diff --git a/submodules/AsyncDisplayKit/Source/Details/Transactions/_ASAsyncTransactionGroup.mm b/submodules/AsyncDisplayKit/Source/_ASAsyncTransactionGroup.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/Details/Transactions/_ASAsyncTransactionGroup.mm rename to submodules/AsyncDisplayKit/Source/_ASAsyncTransactionGroup.mm index ae651d1870..ca170229b8 100644 --- a/submodules/AsyncDisplayKit/Source/Details/Transactions/_ASAsyncTransactionGroup.mm +++ b/submodules/AsyncDisplayKit/Source/_ASAsyncTransactionGroup.mm @@ -12,7 +12,7 @@ #import #import #import -#import +#import "_ASAsyncTransactionContainer+Private.h" @implementation _ASAsyncTransactionGroup { NSHashTable> *_containers; diff --git a/submodules/AsyncDisplayKit/Source/Private/_ASCoreAnimationExtras.mm b/submodules/AsyncDisplayKit/Source/_ASCoreAnimationExtras.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/Private/_ASCoreAnimationExtras.mm rename to submodules/AsyncDisplayKit/Source/_ASCoreAnimationExtras.mm diff --git a/submodules/AsyncDisplayKit/Source/Details/_ASDisplayLayer.mm b/submodules/AsyncDisplayKit/Source/_ASDisplayLayer.mm similarity index 97% rename from submodules/AsyncDisplayKit/Source/Details/_ASDisplayLayer.mm rename to submodules/AsyncDisplayKit/Source/_ASDisplayLayer.mm index a7478f1883..9ad2ca289b 100644 --- a/submodules/AsyncDisplayKit/Source/Details/_ASDisplayLayer.mm +++ b/submodules/AsyncDisplayKit/Source/_ASDisplayLayer.mm @@ -14,10 +14,9 @@ #import #import #import -#import +#import "ASDisplayNodeInternal.h" #import #import -#import @implementation _ASDisplayLayer { @@ -94,7 +93,6 @@ - (void)setNeedsLayout { ASDisplayNodeAssertMainThread(); - as_log_verbose(ASNodeLog(), "%s on %@", sel_getName(_cmd), self); [super setNeedsLayout]; } #endif diff --git a/submodules/AsyncDisplayKit/Source/Details/_ASDisplayView.mm b/submodules/AsyncDisplayKit/Source/_ASDisplayView.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/Details/_ASDisplayView.mm rename to submodules/AsyncDisplayKit/Source/_ASDisplayView.mm index 5225fb9958..365b141bbf 100644 --- a/submodules/AsyncDisplayKit/Source/Details/_ASDisplayView.mm +++ b/submodules/AsyncDisplayKit/Source/_ASDisplayView.mm @@ -8,18 +8,17 @@ // #import -#import +#import "_ASDisplayViewAccessiblity.h" #import #import -#import +#import "ASDisplayNodeInternal.h" #import #import #import #import #import #import -#import #pragma mark - _ASDisplayViewMethodOverrides diff --git a/submodules/AsyncDisplayKit/Source/Details/_ASDisplayViewAccessiblity.h b/submodules/AsyncDisplayKit/Source/_ASDisplayViewAccessiblity.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/Details/_ASDisplayViewAccessiblity.h rename to submodules/AsyncDisplayKit/Source/_ASDisplayViewAccessiblity.h diff --git a/submodules/AsyncDisplayKit/Source/Details/_ASDisplayViewAccessiblity.mm b/submodules/AsyncDisplayKit/Source/_ASDisplayViewAccessiblity.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/Details/_ASDisplayViewAccessiblity.mm rename to submodules/AsyncDisplayKit/Source/_ASDisplayViewAccessiblity.mm index 9e91056aaf..b24e8e9f58 100644 --- a/submodules/AsyncDisplayKit/Source/Details/_ASDisplayViewAccessiblity.mm +++ b/submodules/AsyncDisplayKit/Source/_ASDisplayViewAccessiblity.mm @@ -11,11 +11,9 @@ #import #import -#import #import #import -#import -#import +#import "ASDisplayNodeInternal.h" #import diff --git a/submodules/AsyncDisplayKit/Source/Private/_ASPendingState.h b/submodules/AsyncDisplayKit/Source/_ASPendingState.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/Private/_ASPendingState.h rename to submodules/AsyncDisplayKit/Source/_ASPendingState.h diff --git a/submodules/AsyncDisplayKit/Source/Private/_ASPendingState.mm b/submodules/AsyncDisplayKit/Source/_ASPendingState.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/Private/_ASPendingState.mm rename to submodules/AsyncDisplayKit/Source/_ASPendingState.mm index 9e9778cf04..5bf75e50d1 100644 --- a/submodules/AsyncDisplayKit/Source/Private/_ASPendingState.mm +++ b/submodules/AsyncDisplayKit/Source/_ASPendingState.mm @@ -7,13 +7,13 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import +#import "_ASPendingState.h" #import #import #import #import -#import +#import "ASDisplayNodeInternal.h" #import #define __shouldSetNeedsDisplay(layer) (flags.needsDisplay \ diff --git a/submodules/AsyncDisplayKit/Source/Private/_ASScopeTimer.h b/submodules/AsyncDisplayKit/Source/_ASScopeTimer.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/Private/_ASScopeTimer.h rename to submodules/AsyncDisplayKit/Source/_ASScopeTimer.h diff --git a/submodules/AsyncDisplayKit/Source/asdkmodule.modulemap b/submodules/AsyncDisplayKit/Source/asdkmodule.modulemap deleted file mode 100644 index fd7d49e620..0000000000 --- a/submodules/AsyncDisplayKit/Source/asdkmodule.modulemap +++ /dev/null @@ -1,19 +0,0 @@ -framework module AsyncDisplayKit { - umbrella header "AsyncDisplayKit.h" - - export * - module * { - export * - } - - explicit module ASControlNode_Subclasses { - header "ASControlNode+Subclasses.h" - export * - } - - explicit module ASDisplayNode_Subclasses { - header "ASDisplayNode+Subclasses.h" - export * - } - -} diff --git a/submodules/AsyncDisplayKit/Source/tvOS/ASControlNode+tvOS.mm b/submodules/AsyncDisplayKit/Source/tvOS/ASControlNode+tvOS.mm deleted file mode 100644 index a823d9625c..0000000000 --- a/submodules/AsyncDisplayKit/Source/tvOS/ASControlNode+tvOS.mm +++ /dev/null @@ -1,92 +0,0 @@ -// -// ASControlNode+tvOS.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#if TARGET_OS_TV -#import -#import - -@implementation ASControlNode (tvOS) - -#pragma mark - tvOS -- (void)_pressDown -{ - [UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ - [self setPressedState]; - } completion:^(BOOL finished) { - if (finished) { - [UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ - [self setFocusedState]; - } completion:nil]; - } - }]; -} - -- (BOOL)canBecomeFocused -{ - return YES; -} - -- (BOOL)shouldUpdateFocusInContext:(nonnull UIFocusUpdateContext *)context -{ - return YES; -} - -- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator -{ - //FIXME: This is never valid inside an ASCellNode - if (context.nextFocusedView && context.nextFocusedView == self.view) { - //Focused - [coordinator addCoordinatedAnimations:^{ - [self setFocusedState]; - } completion:nil]; - } else{ - //Not focused - [coordinator addCoordinatedAnimations:^{ - [self setDefaultFocusAppearance]; - } completion:nil]; - } -} - -- (void)setFocusedState -{ - CALayer *layer = self.layer; - layer.shadowOffset = CGSizeMake(2, 10); - [self applyDefaultShadowProperties: layer]; - self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.1, 1.1); -} - -- (void)setPressedState -{ - CALayer *layer = self.layer; - layer.shadowOffset = CGSizeMake(2, 2); - [self applyDefaultShadowProperties: layer]; - self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1); -} - -- (void)applyDefaultShadowProperties:(CALayer *)layer -{ - layer.shadowColor = [UIColor blackColor].CGColor; - layer.shadowRadius = 12.0; - layer.shadowOpacity = 0.45; - layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; -} - -- (void)setDefaultFocusAppearance -{ - CALayer *layer = self.layer; - layer.shadowOffset = CGSizeZero; - layer.shadowColor = [UIColor blackColor].CGColor; - layer.shadowRadius = 0; - layer.shadowOpacity = 0; - layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; - self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1); -} -@end -#endif diff --git a/submodules/AsyncDisplayKit/Source/tvOS/ASImageNode+tvOS.mm b/submodules/AsyncDisplayKit/Source/tvOS/ASImageNode+tvOS.mm deleted file mode 100644 index fa760f1291..0000000000 --- a/submodules/AsyncDisplayKit/Source/tvOS/ASImageNode+tvOS.mm +++ /dev/null @@ -1,191 +0,0 @@ -// -// ASImageNode+tvOS.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#if TARGET_OS_TV -#import -#import - -// TODO: Remove this – we don't need to link GLKit just to convert degrees to radians. -#import -#import - -#import - -@implementation ASImageNode (tvOS) - -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event -{ - [super touchesBegan:touches withEvent:event]; - self.isDefaultFocusAppearance = NO; - UIView *view = [self getView]; - CALayer *layer = view.layer; - - CGSize targetShadowOffset = CGSizeMake(0.0, self.bounds.size.height/8); - [layer removeAllAnimations]; - [CATransaction begin]; - [CATransaction setCompletionBlock:^{ - layer.shadowOffset = targetShadowOffset; - }]; - - CABasicAnimation *shadowOffsetAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOffset"]; - shadowOffsetAnimation.toValue = [NSValue valueWithCGSize:targetShadowOffset]; - shadowOffsetAnimation.duration = 0.4; - shadowOffsetAnimation.removedOnCompletion = NO; - shadowOffsetAnimation.fillMode = kCAFillModeForwards; - shadowOffsetAnimation.timingFunction = [CAMediaTimingFunction functionWithName:@"easeOut"]; - [layer addAnimation:shadowOffsetAnimation forKey:@"shadowOffset"]; - [CATransaction commit]; - - CABasicAnimation *shadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"]; - shadowOpacityAnimation.toValue = [NSNumber numberWithFloat:0.45]; - shadowOpacityAnimation.duration = 0.4; - shadowOpacityAnimation.removedOnCompletion = false; - shadowOpacityAnimation.fillMode = kCAFillModeForwards; - shadowOpacityAnimation.timingFunction = [CAMediaTimingFunction functionWithName:@"easeOut"]; - [layer addAnimation:shadowOpacityAnimation forKey:@"shadowOpacityAnimation"]; - - view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.25, 1.25); - - [CATransaction commit]; -} - -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event -{ - [super touchesMoved:touches withEvent:event]; - - // TODO: Clean up, and improve visuals. - - if (!self.isDefaultFocusAppearance) { - // This view may correspond to either self.view - // or our superview if we are in a ASCellNode - UIView *view = [self getView]; - - UITouch *touch = [touches anyObject]; - // Get the specific point that was touched - - // This is quite messy in it's current state so is not ready for production. - // The reason it is here is for others to contribute and to make it clear what is occuring. - - // We get the touch location in self.view because - // we are operating in that coordinate system. - // BUT we apply our transforms to *view since we want to apply - // the transforms to the root view (L: 107) - CGPoint point = [touch locationInView:self.view]; - CGFloat pitch = 0; - CGFloat yaw = 0; - BOOL topHalf = NO; - if (point.y > CGRectGetHeight(self.view.frame)) { - pitch = 15; - } else if (point.y < -CGRectGetHeight(self.view.frame)) { - pitch = -15; - } else { - pitch = (point.y/CGRectGetHeight(self.view.frame))*15; - } - if (pitch < 0) { - topHalf = YES; - } - - if (point.x > CGRectGetWidth(self.view.frame)) { - yaw = 10; - } else if (point.x < -CGRectGetWidth(self.view.frame)) { - yaw = -10; - } else { - yaw = (point.x/CGRectGetWidth(self.view.frame))*10; - } - if (!topHalf) { - if (yaw > 0) { - yaw = -yaw; - } else { - yaw = fabs(yaw); - } - } - - CATransform3D pitchTransform = CATransform3DMakeRotation(GLKMathDegreesToRadians(pitch),1.0,0.0,0.0); - CATransform3D yawTransform = CATransform3DMakeRotation(GLKMathDegreesToRadians(yaw),0.0,1.0,0.0); - CATransform3D transform = CATransform3DConcat(pitchTransform, yawTransform); - CATransform3D scaleAndTransform = CATransform3DConcat(transform, CATransform3DMakeAffineTransform(CGAffineTransformScale(CGAffineTransformIdentity, 1.25, 1.25))); - - [UIView animateWithDuration:0.5 animations:^{ - view.layer.transform = scaleAndTransform; - }]; - } else { - [self setDefaultFocusAppearance]; - } -} - - -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event -{ - [super touchesEnded:touches withEvent:event]; - [self finishTouches]; -} - -- (void)finishTouches -{ - if (!self.isDefaultFocusAppearance) { - UIView *view = [self getView]; - CALayer *layer = view.layer; - - CGSize targetShadowOffset = CGSizeMake(0.0, self.bounds.size.height/8); - CATransform3D targetScaleTransform = CATransform3DMakeScale(1.2, 1.2, 1.2); - [CATransaction begin]; - [CATransaction setCompletionBlock:^{ - layer.shadowOffset = targetShadowOffset; - }]; - [CATransaction commit]; - - [UIView animateWithDuration:0.4 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ - view.layer.transform = targetScaleTransform; - } completion:^(BOOL finished) { - if (finished) { - [layer removeAnimationForKey:@"shadowOffset"]; - [layer removeAnimationForKey:@"shadowOpacity"]; - } - }]; - } else { - [self setDefaultFocusAppearance]; - } -} - -- (void)setFocusedState -{ - UIView *view = [self getView]; - CALayer *layer = view.layer; - layer.shadowOffset = CGSizeMake(2, 10); - layer.shadowColor = [UIColor blackColor].CGColor; - layer.shadowRadius = 12.0; - layer.shadowOpacity = 0.45; - layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; - view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.25, 1.25); -} - -- (void)setDefaultFocusAppearance -{ - UIView *view = [self getView]; - CALayer *layer = view.layer; - view.transform = CGAffineTransformIdentity; - layer.shadowOpacity = 0; - layer.shadowOffset = CGSizeZero; - layer.shadowRadius = 0; - layer.shadowPath = nil; - [layer removeAnimationForKey:@"shadowOffset"]; - [layer removeAnimationForKey:@"shadowOpacity"]; - self.isDefaultFocusAppearance = YES; -} - -- (UIView *)getView -{ - // TODO: This needs to be re-visited to handle all possibilities. - // If we are inside a ASCellNode, then we need to apply our focus effects to the ASCellNode view/layer rather than the ASImageNode view/layer. - return ASDisplayNodeUltimateParentOfNode(self).view; -} - -@end -#endif diff --git a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKit.xcworkspace/contents.xcworkspacedata b/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKit.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 4d0db5485c..0000000000 --- a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKit.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListKitTestAdapterDataSource.h b/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListKitTestAdapterDataSource.h deleted file mode 100644 index c3cdab0bad..0000000000 --- a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListKitTestAdapterDataSource.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// ASListKitTestAdapterDataSource.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@interface ASListKitTestAdapterDataSource : NSObject - -// array of numbers which is then passed to -[IGListTestSection setItems:] -@property (nonatomic, strong) NSArray *objects; - -@end diff --git a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListKitTestAdapterDataSource.m b/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListKitTestAdapterDataSource.m deleted file mode 100644 index b0cd9a8a6d..0000000000 --- a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListKitTestAdapterDataSource.m +++ /dev/null @@ -1,31 +0,0 @@ -// -// ASListKitTestAdapterDataSource.m -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASListKitTestAdapterDataSource.h" -#import "ASListTestSection.h" - -@implementation ASListKitTestAdapterDataSource - -- (NSArray *)objectsForListAdapter:(IGListAdapter *)listAdapter -{ - return self.objects; -} - -- (IGListSectionController *)listAdapter:(IGListAdapter *)listAdapter sectionControllerForObject:(id)object -{ - ASListTestSection *section = [[ASListTestSection alloc] init]; - return section; -} - -- (nullable UIView *)emptyViewForListAdapter:(IGListAdapter *)listAdapter -{ - return nil; -} - -@end diff --git a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListKitTests.m b/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListKitTests.m deleted file mode 100644 index 1bb2c6fed0..0000000000 --- a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListKitTests.m +++ /dev/null @@ -1,112 +0,0 @@ -// -// ASListKitTests.m -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import "ASListKitTestAdapterDataSource.h" -#import "ASXCTExtensions.h" -#import -#import "ASTestCase.h" - -@interface ASListKitTests : ASTestCase - -@property (nonatomic, strong) ASCollectionNode *collectionNode; -@property (nonatomic, strong) UICollectionView *collectionView; -@property (nonatomic, strong) IGListAdapter *adapter; -@property (nonatomic, strong) ASListKitTestAdapterDataSource *dataSource; -@property (nonatomic, strong) UICollectionViewFlowLayout *layout; -@property (nonatomic, strong) UIWindow *window; -@property (nonatomic) NSInteger reloadDataCount; - -@end - -@implementation ASListKitTests - -- (void)setUp -{ - [super setUp]; - - [ASCollectionView swizzleInstanceMethod:@selector(reloadData) withReplacement:JGMethodReplacementProviderBlock { - return JGMethodReplacement(void, ASCollectionView *) { - JGOriginalImplementation(void); - _reloadDataCount++; - }; - }]; - - self.window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; - - self.layout = [[UICollectionViewFlowLayout alloc] init]; - self.collectionNode = [[ASCollectionNode alloc] initWithCollectionViewLayout:self.layout]; - self.collectionNode.frame = self.window.bounds; - self.collectionView = self.collectionNode.view; - - [self.window addSubnode:self.collectionNode]; - - IGListAdapterUpdater *updater = [[IGListAdapterUpdater alloc] init]; - - self.dataSource = [[ASListKitTestAdapterDataSource alloc] init]; - self.adapter = [[IGListAdapter alloc] initWithUpdater:updater - viewController:nil - workingRangeSize:0]; - self.adapter.dataSource = self.dataSource; - [self.adapter setASDKCollectionNode:self.collectionNode]; - XCTAssertNotNil(self.adapter.collectionView, @"Adapter was not bound to collection view. You may have a stale copy of AsyncDisplayKit that was built without IG_LIST_KIT. Clean Builder Folder IMO."); -} - -- (void)tearDown -{ - [super tearDown]; - XCTAssert([ASCollectionView deswizzleAllMethods]); - self.reloadDataCount = 0; - self.window = nil; - self.collectionNode = nil; - self.collectionView = nil; - self.adapter = nil; - self.dataSource = nil; - self.layout = nil; -} - -- (void)test_whenAdapterUpdated_withObjectsOverflow_thatVisibleObjectsIsSubsetOfAllObjects -{ - // each section controller returns n items sized 100x10 - self.dataSource.objects = @[@1, @2, @3, @4, @5, @6]; - XCTestExpectation *e = [self expectationWithDescription:@"Data update completed"]; - - [self.adapter performUpdatesAnimated:NO completion:^(BOOL finished) { - [e fulfill]; - }]; - - [self waitForExpectationsWithTimeout:1 handler:nil]; - self.collectionNode.view.contentOffset = CGPointMake(0, 30); - [self.collectionNode.view layoutIfNeeded]; - - - NSArray *visibleObjects = [[self.adapter visibleObjects] sortedArrayUsingSelector:@selector(compare:)]; - NSArray *expectedObjects = @[@3, @4, @5]; - XCTAssertEqualObjects(visibleObjects, expectedObjects); -} - -- (void)test_whenCollectionViewIsNotInAWindow_updaterDoesNotJustCallReloadData -{ - [self.collectionView removeFromSuperview]; - - [self.collectionView layoutIfNeeded]; - self.dataSource.objects = @[@1, @2, @3, @4, @5, @6]; - XCTestExpectation *e = [self expectationWithDescription:@"Data update completed"]; - - [self.adapter performUpdatesAnimated:NO completion:^(BOOL finished) { - [e fulfill]; - }]; - [self waitForExpectationsWithTimeout:1 handler:nil]; - [self.collectionView layoutIfNeeded]; - - XCTAssertEqual(self.reloadDataCount, 2); -} - -@end diff --git a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestCellNode.h b/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestCellNode.h deleted file mode 100644 index e0a4ad7f44..0000000000 --- a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestCellNode.h +++ /dev/null @@ -1,14 +0,0 @@ -// -// ASListTestCellNode.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@interface ASListTestCellNode : ASCellNode - -@end diff --git a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestCellNode.m b/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestCellNode.m deleted file mode 100644 index cd3bfb3a8d..0000000000 --- a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestCellNode.m +++ /dev/null @@ -1,14 +0,0 @@ -// -// ASListTestCellNode.m -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASListTestCellNode.h" - -@implementation ASListTestCellNode - -@end diff --git a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestObject.h b/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestObject.h deleted file mode 100644 index 1362ebf560..0000000000 --- a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestObject.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// ASListTestObject.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface ASListTestObject : NSObject - -- (instancetype)initWithKey:(id )key value:(id)value; - -@property (nonatomic, strong, readonly) id key; -@property (nonatomic, strong) id value; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestObject.m b/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestObject.m deleted file mode 100644 index 64cd83649b..0000000000 --- a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestObject.m +++ /dev/null @@ -1,50 +0,0 @@ -// -// ASListTestObject.m -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASListTestObject.h" - -@implementation ASListTestObject - -- (instancetype)initWithKey:(id)key value:(id)value -{ - if (self = [super init]) { - _key = [key copy]; - _value = value; - } - return self; -} - -- (instancetype)copyWithZone:(NSZone *)zone -{ - return [[ASListTestObject alloc] initWithKey:self.key value:self.value]; -} - -#pragma mark - IGListDiffable - -- (id)diffIdentifier -{ - return self.key; -} - -- (BOOL)isEqualToDiffableObject:(id)object -{ - if (object == self) { - return YES; - } - if ([object isKindOfClass:[ASListTestObject class]]) { - id k1 = self.key; - id k2 = [object key]; - id v1 = self.value; - id v2 = [(ASListTestObject *)object value]; - return (v1 == v2 || [v1 isEqual:v2]) && (k1 == k2 || [k1 isEqual:k2]); - } - return NO; -} - -@end diff --git a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestSection.h b/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestSection.h deleted file mode 100644 index d62e21b1f6..0000000000 --- a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestSection.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// ASListTestSection.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -@interface ASListTestSection : IGListSectionController - -@property (nonatomic) NSInteger itemCount; - -@property (nonatomic) NSInteger selectedItemIndex; - -@end diff --git a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestSection.m b/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestSection.m deleted file mode 100644 index edf939b7c0..0000000000 --- a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestSection.m +++ /dev/null @@ -1,61 +0,0 @@ -// -// ASListTestSection.m -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASListTestSection.h" -#import "ASListTestCellNode.h" - -@implementation ASListTestSection - -- (instancetype)init -{ - if (self = [super init]) -{ - _selectedItemIndex = NSNotFound; - } - return self; -} - -- (NSInteger)numberOfItems -{ - return self.itemCount; -} - -- (CGSize)sizeForItemAtIndex:(NSInteger)index -{ - return [ASIGListSectionControllerMethods sizeForItemAtIndex:index]; -} - -- (__kindof UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index -{ - return [ASIGListSectionControllerMethods cellForItemAtIndex:index sectionController:self]; -} - -- (void)didUpdateToObject:(id)object -{ - if ([object isKindOfClass:[NSNumber class]]) -{ - self.itemCount = [object integerValue]; - } -} - -- (void)didSelectItemAtIndex:(NSInteger)index -{ - self.selectedItemIndex = index; -} - -- (ASCellNodeBlock)nodeBlockForItemAtIndex:(NSInteger)index -{ - return ^{ - ASListTestCellNode *node = [[ASListTestCellNode alloc] init]; - node.style.preferredSize = CGSizeMake(100, 10); - return node; - }; -} - -@end diff --git a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestSupplementaryNode.h b/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestSupplementaryNode.h deleted file mode 100644 index c4f40e4ab7..0000000000 --- a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestSupplementaryNode.h +++ /dev/null @@ -1,14 +0,0 @@ -// -// ASListTestSupplementaryNode.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@interface ASListTestSupplementaryNode : ASCellNode - -@end diff --git a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestSupplementaryNode.m b/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestSupplementaryNode.m deleted file mode 100644 index a10f6a8f69..0000000000 --- a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestSupplementaryNode.m +++ /dev/null @@ -1,14 +0,0 @@ -// -// ASListTestSupplementaryNode.m -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASListTestSupplementaryNode.h" - -@implementation ASListTestSupplementaryNode - -@end diff --git a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestSupplementarySource.h b/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestSupplementarySource.h deleted file mode 100644 index acdec1ef94..0000000000 --- a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestSupplementarySource.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// ASListTestSupplementarySource.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -@interface ASListTestSupplementarySource : NSObject - -@property (nonatomic, strong, readwrite) NSArray *supportedElementKinds; - -@property (nonatomic, weak) id collectionContext; - -@property (nonatomic, weak) IGListSectionController *sectionController; - -@end diff --git a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestSupplementarySource.m b/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestSupplementarySource.m deleted file mode 100644 index 07410131e2..0000000000 --- a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/ASListTestSupplementarySource.m +++ /dev/null @@ -1,34 +0,0 @@ -// -// ASListTestSupplementarySource.m -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASListTestSupplementarySource.h" -#import "ASListTestSupplementaryNode.h" - -@implementation ASListTestSupplementarySource - -- (__kindof UICollectionReusableView *)viewForSupplementaryElementOfKind:(NSString *)elementKind atIndex:(NSInteger)index -{ - return [ASIGListSupplementaryViewSourceMethods viewForSupplementaryElementOfKind:elementKind atIndex:index sectionController:self.sectionController]; -} - -- (CGSize)sizeForSupplementaryViewOfKind:(NSString *)elementKind atIndex:(NSInteger)index -{ - return [ASIGListSupplementaryViewSourceMethods sizeForSupplementaryViewOfKind:elementKind atIndex:index]; -} - -- (ASCellNodeBlock)nodeBlockForSupplementaryElementOfKind:(NSString *)elementKind atIndex:(NSInteger)index -{ - return ^{ - ASListTestSupplementaryNode *node = [[ASListTestSupplementaryNode alloc] init]; - node.style.preferredSize = CGSizeMake(100, 10); - return node; - }; -} - -@end diff --git a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/Info.plist b/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/Info.plist deleted file mode 100644 index 6c6c23c43a..0000000000 --- a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/ASDKListKitTests/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1 - - diff --git a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/Podfile b/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/Podfile deleted file mode 100644 index 36498c2473..0000000000 --- a/submodules/AsyncDisplayKit/SubspecWorkspaces/ASDKListKit/Podfile +++ /dev/null @@ -1,9 +0,0 @@ -source 'https://github.com/CocoaPods/Specs.git' - -platform :ios, '9.0' -target 'ASDKListKitTests' do - pod 'Texture/IGListKit', :path => '../..' - pod 'OCMock', '~> 3.4' - pod 'JGMethodSwizzler', :git => 'https://github.com/JonasGessner/JGMethodSwizzler', :branch => 'master' -end - diff --git a/submodules/AsyncDisplayKit/Texture.podspec b/submodules/AsyncDisplayKit/Texture.podspec deleted file mode 100644 index ef915ac597..0000000000 --- a/submodules/AsyncDisplayKit/Texture.podspec +++ /dev/null @@ -1,99 +0,0 @@ -Pod::Spec.new do |spec| - spec.name = 'Texture' - spec.version = '2.8' - spec.license = { :type => 'Apache 2', } - spec.homepage = 'http://texturegroup.org' - spec.authors = { 'Huy Nguyen' => 'hi@huytnguyen.me', 'Garrett Moon' => 'garrett@excitedpixel.com', 'Scott Goodson' => 'scottgoodson@gmail.com', 'Michael Schneider' => 'mischneider1@gmail.com', 'Adlai Holler' => 'adlai@icloud.com' } - spec.summary = 'Smooth asynchronous user interfaces for iOS apps.' - spec.source = { :git => 'https://github.com/TextureGroup/Texture.git', :tag => spec.version.to_s } - spec.module_name = 'AsyncDisplayKit' - spec.header_dir = 'AsyncDisplayKit' - - spec.documentation_url = 'http://texturegroup.org/appledoc/' - - spec.ios.deployment_target = '9.0' - spec.tvos.deployment_target = '9.0' - - # Subspecs - spec.subspec 'Core' do |core| - core.compiler_flags = '-fno-exceptions -Wno-implicit-retain-self' - core.public_header_files = [ - 'Source/*.h', - 'Source/Details/**/*.h', - 'Source/Layout/**/*.h', - 'Source/Base/*.h', - 'Source/Debug/**/*.h', - 'Source/TextKit/ASTextNodeTypes.h', - 'Source/TextKit/ASTextKitComponents.h' - ] - - core.source_files = [ - 'Source/**/*.{h,mm}', - - # Most TextKit components are not public because the C++ content - # in the headers will cause build errors when using - # `use_frameworks!` on 0.39.0 & Swift 2.1. - # See https://github.com/facebook/AsyncDisplayKit/issues/1153 - 'Source/TextKit/*.h', - ] - end - - spec.subspec 'PINRemoteImage' do |pin| - pin.dependency 'PINRemoteImage/iOS', '= 3.0.0-beta.14' - pin.dependency 'PINRemoteImage/PINCache' - pin.dependency 'Texture/Core' - end - - spec.subspec 'IGListKit' do |igl| - igl.dependency 'IGListKit', '~> 3.0' - igl.dependency 'Texture/Core' - end - - spec.subspec 'Yoga' do |yoga| - yoga.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) YOGA=1' } - yoga.dependency 'Yoga', '1.6.0' - yoga.dependency 'Texture/Core' - end - - # If flag is enabled the old TextNode with all dependencies will be compiled out - spec.subspec 'TextNode2' do |text_node| - text_node.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) AS_ENABLE_TEXTNODE=0' } - text_node.dependency 'Texture/Core' - end - - spec.subspec 'Video' do |video| - video.frameworks = ['AVFoundation', 'CoreMedia'] - video.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) AS_USE_VIDEO=1' } - video.dependency 'Texture/Core' - end - - spec.subspec 'MapKit' do |map| - map.frameworks = ['CoreLocation', 'MapKit'] - map.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) AS_USE_MAPKIT=1' } - map.dependency 'Texture/Core' - end - - spec.subspec 'Photos' do |photos| - photos.frameworks = 'Photos' - photos.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) AS_USE_PHOTOS=1' } - photos.dependency 'Texture/Core' - end - - spec.subspec 'AssetsLibrary' do |assetslib| - assetslib.frameworks = 'AssetsLibrary' - assetslib.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) AS_USE_ASSETS_LIBRARY=1' } - assetslib.dependency 'Texture/Core' - end - - # Include these by default for backwards compatibility. - # This will change in 3.0. - spec.default_subspecs = 'Core', 'PINRemoteImage', 'Video', 'MapKit', 'AssetsLibrary', 'Photos' - - spec.social_media_url = 'https://twitter.com/TextureiOS' - spec.library = 'c++' - spec.pod_target_xcconfig = { - 'CLANG_CXX_LANGUAGE_STANDARD' => 'c++11', - 'CLANG_CXX_LIBRARY' => 'libc++' - } - -end diff --git a/submodules/AsyncDisplayKit/buck-files/BUCK_FBSnapshotTestCase b/submodules/AsyncDisplayKit/buck-files/BUCK_FBSnapshotTestCase deleted file mode 100755 index c8b969639e..0000000000 --- a/submodules/AsyncDisplayKit/buck-files/BUCK_FBSnapshotTestCase +++ /dev/null @@ -1,12 +0,0 @@ -apple_library( - name = 'FBSnapshotTestCase', - exported_headers = glob(['FBSnapshotTestCase' + '/**/*.h']), - srcs = glob(['FBSnapshotTestCase' + '/**/*.m']), - frameworks = [ - '$SDKROOT/System/Library/Frameworks/Foundation.framework', - '$SDKROOT/System/Library/Frameworks/UIKit.framework', - '$SDKROOT/System/Library/Frameworks/QuartzCore.framework', - '$PLATFORM_DIR/Developer/Library/Frameworks/XCTest.framework', - ], - visibility = ['PUBLIC'], -) diff --git a/submodules/AsyncDisplayKit/buck-files/BUCK_FLAnimatedImage b/submodules/AsyncDisplayKit/buck-files/BUCK_FLAnimatedImage deleted file mode 100755 index f04abd396b..0000000000 --- a/submodules/AsyncDisplayKit/buck-files/BUCK_FLAnimatedImage +++ /dev/null @@ -1,18 +0,0 @@ -apple_library( - name = 'FLAnimatedImage', - exported_headers = glob(['FLAnimatedImage/*.h']), - srcs = glob(['FLAnimatedImage/*.m']), - preprocessor_flags = ['-fobjc-arc', '-Wno-deprecated-declarations'], - lang_preprocessor_flags = { - 'C': ['-std=gnu99'], - 'CXX': ['-std=gnu++11', '-stdlib=libc++'], - }, - frameworks = [ - '$SDKROOT/System/Library/Frameworks/Foundation.framework', - '$SDKROOT/System/Library/Frameworks/UIKit.framework', - '$SDKROOT/System/Library/Frameworks/ImageIO.framework', - '$SDKROOT/System/Library/Frameworks/MobileCoreServices.framework', - '$SDKROOT/System/Library/Frameworks/QuartzCore.framework', - ], - visibility = ['PUBLIC'], -) diff --git a/submodules/AsyncDisplayKit/buck-files/BUCK_JGMethodSwizzler b/submodules/AsyncDisplayKit/buck-files/BUCK_JGMethodSwizzler deleted file mode 100755 index 169cfa1e01..0000000000 --- a/submodules/AsyncDisplayKit/buck-files/BUCK_JGMethodSwizzler +++ /dev/null @@ -1,9 +0,0 @@ -apple_library( - name = 'JGMethodSwizzler', - exported_headers = ['JGMethodSwizzler' + '/JGMethodSwizzler.h'], - srcs = ['JGMethodSwizzler' + '/JGMethodSwizzler.m'], - frameworks = [ - '$SDKROOT/System/Library/Frameworks/Foundation.framework', - ], - visibility = ['PUBLIC'], -) diff --git a/submodules/AsyncDisplayKit/buck-files/BUCK_OCMock b/submodules/AsyncDisplayKit/buck-files/BUCK_OCMock deleted file mode 100755 index 666f844582..0000000000 --- a/submodules/AsyncDisplayKit/buck-files/BUCK_OCMock +++ /dev/null @@ -1,9 +0,0 @@ -apple_library( - name = 'OCMock', - exported_headers = glob(['Source/OCMock' + '/*.h']), - srcs = glob(['Source/OCMock' + '/*.m']), - frameworks = [ - '$SDKROOT/System/Library/Frameworks/Foundation.framework', - ], - visibility = ['PUBLIC'], -) diff --git a/submodules/AsyncDisplayKit/buck-files/BUCK_PINCache b/submodules/AsyncDisplayKit/buck-files/BUCK_PINCache deleted file mode 100755 index 660b69f716..0000000000 --- a/submodules/AsyncDisplayKit/buck-files/BUCK_PINCache +++ /dev/null @@ -1,23 +0,0 @@ -apple_library( - name = 'PINCache', - exported_headers = glob(['PINCache/*.h']), - # PINDiskCache.m should be compiled with '-fobjc-arc-exceptions' (#105) - srcs = - glob(['PINCache/*.m'], excludes = ['PINCache/PINDiskCache.m']) + - [('PINCache/PINDiskCache.m', ['-fobjc-arc-exceptions'])], - preprocessor_flags = ['-fobjc-arc'], - lang_preprocessor_flags = { - 'C': ['-std=gnu99'], - 'CXX': ['-std=gnu++11', '-stdlib=libc++'], - }, - linker_flags = [ - '-weak_framework', - 'UIKit', - '-weak_framework', - 'AppKit', - ], - frameworks = [ - '$SDKROOT/System/Library/Frameworks/Foundation.framework', - ], - visibility = ['PUBLIC'], -) diff --git a/submodules/AsyncDisplayKit/buck-files/BUCK_PINRemoteImage b/submodules/AsyncDisplayKit/buck-files/BUCK_PINRemoteImage deleted file mode 100755 index 95825e4d95..0000000000 --- a/submodules/AsyncDisplayKit/buck-files/BUCK_PINRemoteImage +++ /dev/null @@ -1,93 +0,0 @@ -##################################### -# Defines -##################################### -COMMON_PREPROCESSOR_FLAGS = ['-fobjc-arc'] - -COMMON_LANG_PREPROCESSOR_FLAGS = { - 'C': ['-std=gnu99'], - 'CXX': ['-std=gnu++11', '-stdlib=libc++'], -} - -FLANIMATEDIMAGE_HEADER_FILES = ['Pod/Classes/Image Categories/FLAnimatedImageView+PINRemoteImage.h'] -FLANIMATEDIMAGE_SOURCE_FILES = ['Pod/Classes/Image Categories/FLAnimatedImageView+PINRemoteImage.m'] - -PINCACHE_HEADER_FILES = glob(['Pod/Classes/PINCache/**/*.h']) -PINCACHE_SOURCE_FILES = glob(['Pod/Classes/PINCache/**/*.m']) - -##################################### -# PINRemoteImage core targets -##################################### -apple_library( - name = 'PINRemoteImage-Core', - header_path_prefix = 'PINRemoteImage', - exported_headers = glob([ - 'Pod/Classes/**/*.h', - ], - excludes = FLANIMATEDIMAGE_HEADER_FILES + PINCACHE_HEADER_FILES - ), - srcs = glob([ - 'Pod/Classes/**/*.m', - ], - excludes = FLANIMATEDIMAGE_SOURCE_FILES + PINCACHE_SOURCE_FILES - ), - preprocessor_flags = COMMON_PREPROCESSOR_FLAGS + [ - '-DPIN_TARGET_IOS=(TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR || TARGET_OS_TV)', - '-DPIN_TARGET_MAC=(TARGET_OS_MAC)', - ], - lang_preprocessor_flags = COMMON_LANG_PREPROCESSOR_FLAGS, - linker_flags = [ - '-weak_framework', - 'UIKit', - '-weak_framework', - 'MobileCoreServices', - '-weak_framework', - 'Cocoa', - '-weak_framework', - 'CoreServices', - ], - frameworks = [ - '$SDKROOT/System/Library/Frameworks/ImageIO.framework', - '$SDKROOT/System/Library/Frameworks/Accelerate.framework', - ], - visibility = ['PUBLIC'], -) - -apple_library( - name = 'PINRemoteImage', - deps = [ - ':PINRemoteImage-FLAnimatedImage', - ':PINRemoteImage-PINCache' - ], - visibility = ['PUBLIC'], -) - -##################################### -# Other PINRemoteImage targets -##################################### -apple_library( - name = 'PINRemoteImage-FLAnimatedImage', - header_path_prefix = 'PINRemoteImage', - exported_headers = FLANIMATEDIMAGE_HEADER_FILES, - srcs = FLANIMATEDIMAGE_SOURCE_FILES, - preprocessor_flags = COMMON_PREPROCESSOR_FLAGS, - deps = [ - ':PINRemoteImage-Core', - '//Pods/FLAnimatedImage:FLAnimatedImage' - ], - visibility = ['PUBLIC'], -) - -apple_library( - name = 'PINRemoteImage-PINCache', - header_path_prefix = 'PINRemoteImage', - exported_headers = PINCACHE_HEADER_FILES, - srcs = PINCACHE_SOURCE_FILES, - preprocessor_flags = COMMON_PREPROCESSOR_FLAGS, - deps = [ - ':PINRemoteImage-Core', - '//Pods/PINCache:PINCache' - ], - visibility = ['PUBLIC'], -) - -#TODO WebP variants diff --git a/submodules/AsyncDisplayKit/build.sh b/submodules/AsyncDisplayKit/build.sh deleted file mode 100755 index e866f305af..0000000000 --- a/submodules/AsyncDisplayKit/build.sh +++ /dev/null @@ -1,212 +0,0 @@ -#!/bin/bash - -PLATFORM="${TEXTURE_BUILD_PLATFORM:-platform=iOS Simulator,OS=10.2,name=iPhone 7}" -SDK="${TEXTURE_BUILD_SDK:-iphonesimulator11.4}" -DERIVED_DATA_PATH="~/ASDKDerivedData" - -# It is pitch black. -set -e -function trap_handler { - echo -e "\n\nOh no! You walked directly into the slavering fangs of a lurking grue!" - echo "**** You have died ****" - exit 255 -} -trap trap_handler INT TERM EXIT - -# Derived data handling -eval [ ! -d $DERIVED_DATA_PATH ] && eval mkdir $DERIVED_DATA_PATH -function clean_derived_data { - eval find $DERIVED_DATA_PATH -mindepth 1 -delete -} - -# Build example -function build_example { - example="$1" - - clean_derived_data - - if [ -f "${example}/Podfile" ]; then - echo "Using CocoaPods" - if [ -f "${example}/Podfile.lock" ]; then - rm "$example/Podfile.lock" - fi - rm -rf "$example/Pods" - pod install --project-directory=$example - - set -o pipefail && xcodebuild \ - -workspace "${example}/Sample.xcworkspace" \ - -scheme Sample \ - -sdk "$SDK" \ - -destination "$PLATFORM" \ - -derivedDataPath "$DERIVED_DATA_PATH" \ - build | xcpretty $FORMATTER - elif [ -f "${example}/Cartfile" ]; then - echo "Using Carthage" - local_repo=`pwd` - current_branch=`git rev-parse --abbrev-ref HEAD` - cd $example - - echo "git \"file://${local_repo}\" \"${current_branch}\"" > "Cartfile" - carthage update --platform iOS - - set -o pipefail && xcodebuild \ - -project "Sample.xcodeproj" \ - -scheme Sample \ - -sdk "$SDK" \ - -destination "$PLATFORM" \ - build | xcpretty $FORMATTER - - cd ../.. - fi -} - -MODE="$1" - -if type xcpretty-travis-formatter &> /dev/null; then - FORMATTER="-f $(xcpretty-travis-formatter)" - else - FORMATTER="-s" -fi - -if [ "$MODE" = "tests" -o "$MODE" = "all" ]; then - echo "Building & testing AsyncDisplayKit." - pod install - set -o pipefail && xcodebuild \ - -workspace AsyncDisplayKit.xcworkspace \ - -scheme AsyncDisplayKit \ - -sdk "$SDK" \ - -destination "$PLATFORM" \ - build-for-testing test | xcpretty $FORMATTER - success="1" -fi - -if [ "$MODE" = "tests_listkit" ]; then - echo "Building & testing AsyncDisplayKit+IGListKit." - pod install --project-directory=SubspecWorkspaces/ASDKListKit - set -o pipefail && xcodebuild \ - -workspace SubspecWorkspaces/ASDKListKit/ASDKListKit.xcworkspace \ - -scheme ASDKListKitTests \ - -sdk "$SDK" \ - -destination "$PLATFORM" \ - build-for-testing test | xcpretty $FORMATTER - success="1" -fi - -if [ "$MODE" = "examples" -o "$MODE" = "all" ]; then - echo "Verifying that all AsyncDisplayKit examples compile." - #Update cocoapods repo - pod repo update master - - for example in examples/*/; do - echo "Building (examples) $example." - - build_example $example - done - success="1" -fi - -if [ "$MODE" = "examples-pt1" ]; then - echo "Verifying that all AsyncDisplayKit examples compile." - #Update cocoapods repo - pod repo update master - - for example in $((find ./examples -type d -maxdepth 1 \( ! -iname ".*" \)) | head -6 | head); do - echo "Building (examples-pt1) $example." - - build_example $example - done - success="1" -fi - -if [ "$MODE" = "examples-pt2" ]; then - echo "Verifying that all AsyncDisplayKit examples compile." - #Update cocoapods repo - pod repo update master - - for example in $((find ./examples -type d -maxdepth 1 \( ! -iname ".*" \)) | head -12 | tail -6 | head); do - echo "Building $example (examples-pt2)." - - build_example $example - done - success="1" -fi - -if [ "$MODE" = "examples-pt3" ]; then - echo "Verifying that all AsyncDisplayKit examples compile." - #Update cocoapods repo - pod repo update master - - for example in $((find ./examples -type d -maxdepth 1 \( ! -iname ".*" \)) | head -7 | head); do - echo "Building $example (examples-pt3)." - - build_example $example - done - success="1" -fi - -if [ "$MODE" = "examples-extra" ]; then - echo "Verifying that all AsyncDisplayKit examples compile." - #Update cocoapods repo - pod repo update master - - for example in $((find ./examples_extra -type d -maxdepth 1 \( ! -iname ".*" \)) | head -7 | head); do - echo "Building $example (examples-extra)." - - build_example $example - done - success="1" -fi - -# Support building a specific example: sh build.sh example examples/ASDKLayoutTransition -if [ "$MODE" = "example" ]; then - echo "Verifying that all AsyncDisplayKit examples compile." - #Update cocoapods repo - pod repo update master - - build_example $2 - success="1" -fi - -if [ "$MODE" = "life-without-cocoapods" -o "$MODE" = "all" ]; then - echo "Verifying that AsyncDisplayKit functions as a static library." - - set -o pipefail && xcodebuild \ - -workspace "smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcworkspace" \ - -scheme "Life Without CocoaPods" \ - -sdk "$SDK" \ - -destination "$PLATFORM" \ - build | xcpretty $FORMATTER - success="1" -fi - -if [ "$MODE" = "framework" -o "$MODE" = "all" ]; then - echo "Verifying that AsyncDisplayKit functions as a dynamic framework (for Swift/Carthage users)." - - set -o pipefail && xcodebuild \ - -project "smoke-tests/Framework/Sample.xcodeproj" \ - -scheme Sample \ - -sdk "$SDK" \ - -destination "$PLATFORM" \ - build | xcpretty $FORMATTER - success="1" -fi - -if [ "$MODE" = "cocoapods-lint" -o "$MODE" = "all" ]; then - echo "Verifying that podspec lints." - - set -o pipefail && pod env && pod lib lint --allow-warnings - success="1" -fi - -if [ "$MODE" = "carthage" -o "$MODE" = "all" ]; then - echo "Verifying carthage works." - - set -o pipefail && carthage update && carthage build --no-skip-current -fi - -if [ "$success" = "1" ]; then - trap - EXIT - exit 0 -fi - -echo "Unrecognised mode '$MODE'." diff --git a/submodules/AsyncDisplayKit/plans/LayoutDebugger/Overview.md b/submodules/AsyncDisplayKit/plans/LayoutDebugger/Overview.md deleted file mode 100644 index 593cbb1a79..0000000000 --- a/submodules/AsyncDisplayKit/plans/LayoutDebugger/Overview.md +++ /dev/null @@ -1,43 +0,0 @@ -# Layout Debugger - -## Motivation - -The layout system is arguably one of the hardest parts to deal with in the framework. There are many factors, such as constrained size range, preferred size and flex properties, that make a layout spec to behave in a certain way. As a result, it's often hard to debug a layout, as well as to help debugging because we have to grab the whole context of the layout in order to pinpoint the problem at hand. Currently we have a couple of tools to help address this issue, namely ASCII art string and the layout inspector project from @hannahmbanana. - -While the layout inspector project is definitely a step toward the right direction and was proven to be helpful, I think we can do a lot more. One drawback of that project is that we didn't have a clear integration contract with the core components of the framework. We ended up introducing lots of complexities to ASDisplayNode that were hard to reason and mantain. Another problem is it was difficult to bootstrap the tool into existing code/layout. - -This project aims to provide a functional layout debugging solution that requires minimal change to the core framework, is easy to setup and works out-of-the-box on existing applications. These will be achieved by building an extension framework that is hosted in a separate repository, integrates with Texture core and leverages Chrome DevTools. - -## Execution plan - -At first, we'll build on top of [PonyDebugger](https://github.com/square/PonyDebugger). I played with it for a few hours and [the result](https://www.dropbox.com/s/8bcpdgogoewmox9/view%20debugger.mov?dl=0) looks promising. The bottom line is that we'll implement a new `PDDomainController` that is inspired by `PDDOMDomainController` but is catered toward `ASLayoutElement`. The controller should expose style properties, allow editting those properties and, as a result, support hot reloading. It should also expose the constrained size passed to each element during the previous layout pass. - -Eventually, we'll possibly move away from PonyDebugger and implement our own framework due to a few reasons: - -1. PonyDebugger is not actively maintained. That might be because it's considered a "done" project, although the amount of open issues may suggest otherwise. -2. It's not easy to setup the environment, especially because of the [ponyd gateway server](https://github.com/square/PonyDebugger/tree/master/ponyd). Ponyd is essentially a middleman that sits between the client code and Chrome DevTools. It is implemented in Python and hosts its own version of DevTools. Its bootstrap script is more or less broken. It's not trivial (at least for me) to setup a working environment. Instead, my limitted research showed that we can do better by letting the client app be a mDNS broadcaster and allowing Chrome DevTools to connect to it. The workflow will be very straight-forward for developers, similar to [Stetho](https://facebook.github.io/stetho/)'s. In addition, the whole project will be simpler because we don't need to maintain ponyd. -3. It contains other features besides layout debugging, such as network monitoring and remote logging. While they are absolutely useful, they are not in the scope of this project and add complexities to it. - -## Integration with Texture (core) - -As mentioned above, this framework will be a seperate project that integrates with Texture. Most of the changes in Texture's components, like `ASLayoutElement`, `ASDisplayNode` and `ASLayoutSpec`s, will be implemented as extensions inside the debugger framework. We'll try as much as we can to minimalize changes in Texture (core) that are needed to support this project. - -## Technical issues related to Texture (core) - -There are a few technical difficulties that we need to address: - -- Layout spec flattening: Currently `ASDisplayNode` flattens its layout tree right after it receives an `ASLayout`. As a result, `ASLayoutSpec` objects are discarded and are not available for inspecting/debugging. My current solution is introducing a new `shouldSkipFlattening` flag that tells `ASDisplayNode` to keep its layout tree as is. This flag defaults to `NO`. In addition, we need to update `-layoutSublayouts` to skip any non-node objects in the tree. We should avoid introducing runtime overheads to production code and projects that don't use the debugger. -- Style properties overrding: It's common for client code to set flex properties to subnodes inside `-layoutSpecThatFits:`. Doing this will override any values set to these properties by the debugger right before a layout pass which is needed for these changes to be taken into account. My current idea is adding a special `style` object that is loaded once from the exising object and can be changed by the debugger. This special object will be preferred over the built-in one when it's time to calculate a new layout. -- Manual layout is not supported: Layouts that are done manually (via `-calculateSizeThatFits:` and `-layout`) can't be updated by the debugger. Nodes inside these layouts are still available for inspecting though. - -## Long-term ideas - -Once we have a functional debugger with a solid foundation, we can start exploring below ideas: - -- Remote debugging: Since the client app is a mDNS broadcaster, I *think* it's possible to support remote debugging as well as pair programming: "I have a layout issue" "Let me connect to your runtime and inspect it". Crazy I know! Inspired by this [Chrome extension](https://github.com/auchenberg/devtools-remote). -- Layout spec injecting: We may try to abstract `-layoutSpecThatFits:` in such a way that the entire layout specification of a node is not only defined within the class but can be loaded (or manipulated) elsewhere, be it from the debugger or even a backend server. - -## Naming - -I'm planning to call this project "Texture Debugger". It'll be a suite of debugging tools tailored mainly for Texture framework. - diff --git a/submodules/AsyncDisplayKit/run_tests_update_status.sh b/submodules/AsyncDisplayKit/run_tests_update_status.sh deleted file mode 100755 index 3f457c07ce..0000000000 --- a/submodules/AsyncDisplayKit/run_tests_update_status.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail - -UPDATE_STATUS_PATH=$1 -BUILDKITE_PULL_REQUEST=$2 -BUILDKITE_BUILD_URL=$3 - -function updateStatus() { - if [ "${BUILDKITE_PULL_REQUEST}" != "false" ] ; then - ${UPDATE_STATUS_PATH} "TextureGroup" "Texture" ${BUILDKITE_PULL_REQUEST} "$1" ${BUILDKITE_BUILD_URL} "$2" "CI/Pinterest" "$3" - fi -} - -if [[ -z "${UPDATE_STATUS_PATH}" || -z "${BUILDKITE_PULL_REQUEST}" || -z "${BUILDKITE_BUILD_URL}" ]] ; then - echo "Update status path (${UPDATE_STATUS_PATH}), pull request (${BUILDKITE_BUILD_URL}) or build url (${BUILDKITE_PULL_REQUEST}) unset." - trap - EXIT - exit 255 -fi - -trapped="false" -function trap_handler() { - if [[ "$trapped" = "false" ]]; then - updateStatus "failure" "Tests failed…" `pwd`/log.txt - echo "Tests failed, updated status to failure" - rm `pwd`/log.txt - fi - trapped="true" -} -trap trap_handler INT TERM EXIT - -updateStatus "pending" "Starting build…" - -echo "--- Running Danger" -bundle exec danger --verbose 2>&1|tee `pwd`/log.txt - -./build.sh all 2>&1|tee `pwd`/log.txt - -rm `pwd`/log.txt - -updateStatus "success" "Tests passed" - -echo "All tests succeeded, updated status to success" -trap - EXIT -exit 0 \ No newline at end of file diff --git a/submodules/AsyncDisplayKit/smoke-tests/Framework/Default-568h@2x.png b/submodules/AsyncDisplayKit/smoke-tests/Framework/Default-568h@2x.png deleted file mode 100644 index 1547a98454..0000000000 Binary files a/submodules/AsyncDisplayKit/smoke-tests/Framework/Default-568h@2x.png and /dev/null differ diff --git a/submodules/AsyncDisplayKit/smoke-tests/Framework/Default-667h@2x.png b/submodules/AsyncDisplayKit/smoke-tests/Framework/Default-667h@2x.png deleted file mode 100644 index 988ea56bab..0000000000 Binary files a/submodules/AsyncDisplayKit/smoke-tests/Framework/Default-667h@2x.png and /dev/null differ diff --git a/submodules/AsyncDisplayKit/smoke-tests/Framework/Default-736h@3x.png b/submodules/AsyncDisplayKit/smoke-tests/Framework/Default-736h@3x.png deleted file mode 100644 index d19eb325a2..0000000000 Binary files a/submodules/AsyncDisplayKit/smoke-tests/Framework/Default-736h@3x.png and /dev/null differ diff --git a/submodules/AsyncDisplayKit/smoke-tests/Framework/README.md b/submodules/AsyncDisplayKit/smoke-tests/Framework/README.md deleted file mode 100644 index cce2cb22fb..0000000000 --- a/submodules/AsyncDisplayKit/smoke-tests/Framework/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# "Framework" - -This is a very simple pseudo-"integration test" project that links against -AsyncDisplayKit as a dynamic framework, for Swift/Carthage users. - -If it fails to compile, Travis CI builds will fail. To escape from such dire straits: - -* If you've added a new class intended for public use, make sure you added its - header to the "Public" group of the "Headers" build phase in the - AsyncDisplayKit-iOS framework target. Note that this smoke test will only fail - if you remembered to add your new file to the umbrella helper. diff --git a/submodules/AsyncDisplayKit/smoke-tests/Framework/Sample/AppDelegate.swift b/submodules/AsyncDisplayKit/smoke-tests/Framework/Sample/AppDelegate.swift deleted file mode 100644 index a2c10adbba..0000000000 --- a/submodules/AsyncDisplayKit/smoke-tests/Framework/Sample/AppDelegate.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// AppDelegate.swift -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -import UIKit - -@UIApplicationMain -class AppDelegate: UIResponder, UIApplicationDelegate { - - var window: UIWindow? - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { - let window = UIWindow(frame: UIScreen.main.bounds) - window.backgroundColor = UIColor.white - window.rootViewController = ViewController(nibName: nil, bundle: nil) - window.makeKeyAndVisible() - self.window = window - return true - } - -} diff --git a/submodules/AsyncDisplayKit/smoke-tests/Framework/Sample/Info.plist b/submodules/AsyncDisplayKit/smoke-tests/Framework/Sample/Info.plist deleted file mode 100644 index fb4115c84c..0000000000 --- a/submodules/AsyncDisplayKit/smoke-tests/Framework/Sample/Info.plist +++ /dev/null @@ -1,36 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/submodules/AsyncDisplayKit/smoke-tests/Framework/Sample/ViewController.swift b/submodules/AsyncDisplayKit/smoke-tests/Framework/Sample/ViewController.swift deleted file mode 100644 index 9d96ac7477..0000000000 --- a/submodules/AsyncDisplayKit/smoke-tests/Framework/Sample/ViewController.swift +++ /dev/null @@ -1,61 +0,0 @@ -// -// ViewController.swift -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -import UIKit -import AsyncDisplayKit - -class ViewController: UIViewController, ASTableDataSource, ASTableDelegate { - - var tableNode: ASTableNode - - - // MARK: UIViewController. - - override required init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { - self.tableNode = ASTableNode() - - super.init(nibName: nil, bundle: nil) - - self.tableNode.dataSource = self - self.tableNode.delegate = self - } - - required init(coder aDecoder: NSCoder) { - fatalError("storyboards are incompatible with truth and beauty") - } - - override func viewDidLoad() { - super.viewDidLoad() - self.view.addSubview(self.tableNode.view) - } - - override func viewWillLayoutSubviews() { - self.tableNode.frame = self.view.bounds - } - - - // MARK: ASTableView data source and delegate. - - func tableNode(_ tableNode: ASTableNode, nodeForRowAt indexPath: IndexPath) -> ASCellNode { - let patter = NSString(format: "[%ld.%ld] says hello!", indexPath.section, indexPath.row) - let node = ASTextCellNode() - node.text = patter as String - - return node - } - - func numberOfSections(in tableNode: ASTableNode) -> Int { - return 1 - } - - func tableNode(_ tableNode: ASTableNode, numberOfRowsInSection section: Int) -> Int { - return 20 - } - -} diff --git a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life With Frameworks/Assets.xcassets/AppIcon.appiconset/Contents.json b/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life With Frameworks/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index eeea76c2db..0000000000 --- a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life With Frameworks/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "3x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "83.5x83.5", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life With Frameworks/Base.lproj/LaunchScreen.storyboard b/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life With Frameworks/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index ebf48f6039..0000000000 --- a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life With Frameworks/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life With Frameworks/Info.plist b/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life With Frameworks/Info.plist deleted file mode 100644 index eabb3ae346..0000000000 --- a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life With Frameworks/Info.plist +++ /dev/null @@ -1,45 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life With Frameworks/main.m b/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life With Frameworks/main.m deleted file mode 100644 index feb53cc3f7..0000000000 --- a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life With Frameworks/main.m +++ /dev/null @@ -1,16 +0,0 @@ -// -// main.m -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import "AppDelegate.h" - -int main(int argc, char * argv[]) { - @autoreleasepool { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); - } -} diff --git a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/project.pbxproj b/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/project.pbxproj deleted file mode 100644 index dd795b7545..0000000000 --- a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/project.pbxproj +++ /dev/null @@ -1,555 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 058968F51ABCE06E0059CE2A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 058968F41ABCE06E0059CE2A /* main.m */; }; - 058968F81ABCE06E0059CE2A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 058968F71ABCE06E0059CE2A /* AppDelegate.m */; }; - 058968FB1ABCE06E0059CE2A /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 058968FA1ABCE06E0059CE2A /* ViewController.m */; }; - 0589691B1ABCE0E80059CE2A /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 058969181ABCE0E80059CE2A /* Default-568h@2x.png */; }; - 0589691C1ABCE0E80059CE2A /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 058969191ABCE0E80059CE2A /* Default-667h@2x.png */; }; - 0589691D1ABCE0E80059CE2A /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0589691A1ABCE0E80059CE2A /* Default-736h@3x.png */; }; - 0589692A1ABCE17C0059CE2A /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058969291ABCE17C0059CE2A /* AssetsLibrary.framework */; }; - 0589692C1ABCE1820059CE2A /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0589692B1ABCE1820059CE2A /* Photos.framework */; }; - 92DD2FEC1BF4D8BB0074C9DD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DD2FEB1BF4D8BB0074C9DD /* MapKit.framework */; }; - F729B8BB1D2E176700C9EDBC /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = F729B8BA1D2E176700C9EDBC /* main.m */; }; - F729B8C61D2E176700C9EDBC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F729B8C51D2E176700C9EDBC /* Assets.xcassets */; }; - F729B8C91D2E176700C9EDBC /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F729B8C71D2E176700C9EDBC /* LaunchScreen.storyboard */; }; - F729B8D11D2E17A300C9EDBC /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 058968F71ABCE06E0059CE2A /* AppDelegate.m */; }; - F729B8D21D2E17A300C9EDBC /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 058968FA1ABCE06E0059CE2A /* ViewController.m */; }; - F7CE6CB61D2CE00800BE4C15 /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F7CE6CAD1D2CDFFB00BE4C15 /* AsyncDisplayKit.framework */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - F729B8D51D2E17C800C9EDBC /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = F7CE6CA51D2CDFFB00BE4C15 /* AsyncDisplayKit.xcodeproj */; - proxyType = 1; - remoteGlobalIDString = B35061D91B010EDF0018CF92; - remoteInfo = "AsyncDisplayKit-iOS"; - }; - F7CE6CAC1D2CDFFB00BE4C15 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = F7CE6CA51D2CDFFB00BE4C15 /* AsyncDisplayKit.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 058D09AC195D04C000B7D73C; - remoteInfo = AsyncDisplayKit; - }; - F7CE6CAE1D2CDFFB00BE4C15 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = F7CE6CA51D2CDFFB00BE4C15 /* AsyncDisplayKit.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 058D09BC195D04C000B7D73C; - remoteInfo = AsyncDisplayKitTests; - }; - F7CE6CB01D2CDFFB00BE4C15 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = F7CE6CA51D2CDFFB00BE4C15 /* AsyncDisplayKit.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 057D02BF1AC0A66700C7AC3C; - remoteInfo = AsyncDisplayKitTestHost; - }; - F7CE6CB41D2CE00300BE4C15 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = F7CE6CA51D2CDFFB00BE4C15 /* AsyncDisplayKit.xcodeproj */; - proxyType = 1; - remoteGlobalIDString = B35061D91B010EDF0018CF92; - remoteInfo = AsyncDisplayKit; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - F729B8D71D2E17C800C9EDBC /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 058968EF1ABCE06E0059CE2A /* Life Without CocoaPods.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Life Without CocoaPods.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - 058968F31ABCE06E0059CE2A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 058968F41ABCE06E0059CE2A /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - 058968F61ABCE06E0059CE2A /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - 058968F71ABCE06E0059CE2A /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - 058968F91ABCE06E0059CE2A /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; - 058968FA1ABCE06E0059CE2A /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; - 058969181ABCE0E80059CE2A /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; - 058969191ABCE0E80059CE2A /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = ""; }; - 0589691A1ABCE0E80059CE2A /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = ""; }; - 058969291ABCE17C0059CE2A /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = System/Library/Frameworks/AssetsLibrary.framework; sourceTree = SDKROOT; }; - 0589692B1ABCE1820059CE2A /* Photos.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Photos.framework; path = System/Library/Frameworks/Photos.framework; sourceTree = SDKROOT; }; - 92DD2FEB1BF4D8BB0074C9DD /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; }; - F729B8B71D2E176700C9EDBC /* Life With Frameworks.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Life With Frameworks.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - F729B8BA1D2E176700C9EDBC /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - F729B8C51D2E176700C9EDBC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - F729B8C81D2E176700C9EDBC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - F729B8CA1D2E176700C9EDBC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - F7CE6CA51D2CDFFB00BE4C15 /* AsyncDisplayKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = AsyncDisplayKit.xcodeproj; path = ../../AsyncDisplayKit.xcodeproj; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 058968EC1ABCE06E0059CE2A /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - F7CE6CB61D2CE00800BE4C15 /* AsyncDisplayKit.framework in Frameworks */, - 92DD2FEC1BF4D8BB0074C9DD /* MapKit.framework in Frameworks */, - 0589692C1ABCE1820059CE2A /* Photos.framework in Frameworks */, - 0589692A1ABCE17C0059CE2A /* AssetsLibrary.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - F729B8B41D2E176700C9EDBC /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 058968E61ABCE06E0059CE2A = { - isa = PBXGroup; - children = ( - 92DD2FEB1BF4D8BB0074C9DD /* MapKit.framework */, - 0589692B1ABCE1820059CE2A /* Photos.framework */, - 058969291ABCE17C0059CE2A /* AssetsLibrary.framework */, - F7CE6CA51D2CDFFB00BE4C15 /* AsyncDisplayKit.xcodeproj */, - 058968F11ABCE06E0059CE2A /* Life Without CocoaPods */, - F729B8B81D2E176700C9EDBC /* Life With Frameworks */, - 058968F01ABCE06E0059CE2A /* Products */, - ); - indentWidth = 2; - sourceTree = ""; - tabWidth = 2; - usesTabs = 0; - }; - 058968F01ABCE06E0059CE2A /* Products */ = { - isa = PBXGroup; - children = ( - 058968EF1ABCE06E0059CE2A /* Life Without CocoaPods.app */, - F729B8B71D2E176700C9EDBC /* Life With Frameworks.app */, - ); - name = Products; - sourceTree = ""; - }; - 058968F11ABCE06E0059CE2A /* Life Without CocoaPods */ = { - isa = PBXGroup; - children = ( - 058968F61ABCE06E0059CE2A /* AppDelegate.h */, - 058968F71ABCE06E0059CE2A /* AppDelegate.m */, - 058968F91ABCE06E0059CE2A /* ViewController.h */, - 058968FA1ABCE06E0059CE2A /* ViewController.m */, - 058968F21ABCE06E0059CE2A /* Supporting Files */, - ); - path = "Life Without CocoaPods"; - sourceTree = ""; - }; - 058968F21ABCE06E0059CE2A /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 058969181ABCE0E80059CE2A /* Default-568h@2x.png */, - 058969191ABCE0E80059CE2A /* Default-667h@2x.png */, - 0589691A1ABCE0E80059CE2A /* Default-736h@3x.png */, - 058968F31ABCE06E0059CE2A /* Info.plist */, - 058968F41ABCE06E0059CE2A /* main.m */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - F729B8B81D2E176700C9EDBC /* Life With Frameworks */ = { - isa = PBXGroup; - children = ( - F729B8C51D2E176700C9EDBC /* Assets.xcassets */, - F729B8C71D2E176700C9EDBC /* LaunchScreen.storyboard */, - F729B8CA1D2E176700C9EDBC /* Info.plist */, - F729B8B91D2E176700C9EDBC /* Supporting Files */, - ); - path = "Life With Frameworks"; - sourceTree = ""; - }; - F729B8B91D2E176700C9EDBC /* Supporting Files */ = { - isa = PBXGroup; - children = ( - F729B8BA1D2E176700C9EDBC /* main.m */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - F7CE6CA61D2CDFFB00BE4C15 /* Products */ = { - isa = PBXGroup; - children = ( - F7CE6CAD1D2CDFFB00BE4C15 /* AsyncDisplayKit.framework */, - F7CE6CAF1D2CDFFB00BE4C15 /* AsyncDisplayKitTests.xctest */, - F7CE6CB11D2CDFFB00BE4C15 /* AsyncDisplayKitTestHost.app */, - ); - name = Products; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 058968EE1ABCE06E0059CE2A /* Life Without CocoaPods */ = { - isa = PBXNativeTarget; - buildConfigurationList = 058969121ABCE06E0059CE2A /* Build configuration list for PBXNativeTarget "Life Without CocoaPods" */; - buildPhases = ( - 058968EB1ABCE06E0059CE2A /* Sources */, - 058968EC1ABCE06E0059CE2A /* Frameworks */, - 058968ED1ABCE06E0059CE2A /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - F7CE6CB51D2CE00300BE4C15 /* PBXTargetDependency */, - ); - name = "Life Without CocoaPods"; - productName = "Life Without CocoaPods"; - productReference = 058968EF1ABCE06E0059CE2A /* Life Without CocoaPods.app */; - productType = "com.apple.product-type.application"; - }; - F729B8B61D2E176700C9EDBC /* Life With Frameworks */ = { - isa = PBXNativeTarget; - buildConfigurationList = F729B8D01D2E176700C9EDBC /* Build configuration list for PBXNativeTarget "Life With Frameworks" */; - buildPhases = ( - F729B8B31D2E176700C9EDBC /* Sources */, - F729B8B41D2E176700C9EDBC /* Frameworks */, - F729B8B51D2E176700C9EDBC /* Resources */, - F729B8D71D2E17C800C9EDBC /* Embed Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - F729B8D61D2E17C800C9EDBC /* PBXTargetDependency */, - ); - name = "Life With Frameworks"; - productName = "Life With Frameworks"; - productReference = F729B8B71D2E176700C9EDBC /* Life With Frameworks.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 058968E71ABCE06E0059CE2A /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0730; - ORGANIZATIONNAME = Facebook; - TargetAttributes = { - 058968EE1ABCE06E0059CE2A = { - CreatedOnToolsVersion = 6.2; - }; - F729B8B61D2E176700C9EDBC = { - CreatedOnToolsVersion = 7.3.1; - }; - }; - }; - buildConfigurationList = 058968EA1ABCE06E0059CE2A /* Build configuration list for PBXProject "Life Without CocoaPods" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 058968E61ABCE06E0059CE2A; - productRefGroup = 058968F01ABCE06E0059CE2A /* Products */; - projectDirPath = ""; - projectReferences = ( - { - ProductGroup = F7CE6CA61D2CDFFB00BE4C15 /* Products */; - ProjectRef = F7CE6CA51D2CDFFB00BE4C15 /* AsyncDisplayKit.xcodeproj */; - }, - ); - projectRoot = ""; - targets = ( - 058968EE1ABCE06E0059CE2A /* Life Without CocoaPods */, - F729B8B61D2E176700C9EDBC /* Life With Frameworks */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXReferenceProxy section */ - F7CE6CAD1D2CDFFB00BE4C15 /* AsyncDisplayKit.framework */ = { - isa = PBXReferenceProxy; - fileType = wrapper.framework; - path = AsyncDisplayKit.framework; - remoteRef = F7CE6CAC1D2CDFFB00BE4C15 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - F7CE6CAF1D2CDFFB00BE4C15 /* AsyncDisplayKitTests.xctest */ = { - isa = PBXReferenceProxy; - fileType = wrapper.cfbundle; - path = AsyncDisplayKitTests.xctest; - remoteRef = F7CE6CAE1D2CDFFB00BE4C15 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - F7CE6CB11D2CDFFB00BE4C15 /* AsyncDisplayKitTestHost.app */ = { - isa = PBXReferenceProxy; - fileType = wrapper.application; - path = AsyncDisplayKitTestHost.app; - remoteRef = F7CE6CB01D2CDFFB00BE4C15 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; -/* End PBXReferenceProxy section */ - -/* Begin PBXResourcesBuildPhase section */ - 058968ED1ABCE06E0059CE2A /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 0589691B1ABCE0E80059CE2A /* Default-568h@2x.png in Resources */, - 0589691C1ABCE0E80059CE2A /* Default-667h@2x.png in Resources */, - 0589691D1ABCE0E80059CE2A /* Default-736h@3x.png in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - F729B8B51D2E176700C9EDBC /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - F729B8C91D2E176700C9EDBC /* LaunchScreen.storyboard in Resources */, - F729B8C61D2E176700C9EDBC /* Assets.xcassets in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 058968EB1ABCE06E0059CE2A /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 058968FB1ABCE06E0059CE2A /* ViewController.m in Sources */, - 058968F81ABCE06E0059CE2A /* AppDelegate.m in Sources */, - 058968F51ABCE06E0059CE2A /* main.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - F729B8B31D2E176700C9EDBC /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - F729B8D21D2E17A300C9EDBC /* ViewController.m in Sources */, - F729B8D11D2E17A300C9EDBC /* AppDelegate.m in Sources */, - F729B8BB1D2E176700C9EDBC /* main.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - F729B8D61D2E17C800C9EDBC /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = "AsyncDisplayKit-iOS"; - targetProxy = F729B8D51D2E17C800C9EDBC /* PBXContainerItemProxy */; - }; - F7CE6CB51D2CE00300BE4C15 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = AsyncDisplayKit; - targetProxy = F7CE6CB41D2CE00300BE4C15 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - F729B8C71D2E176700C9EDBC /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - F729B8C81D2E176700C9EDBC /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 058969101ABCE06E0059CE2A /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_GENERATE_TEST_COVERAGE_FILES = YES; - GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - }; - name = Debug; - }; - 058969111ABCE06E0059CE2A /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_GENERATE_TEST_COVERAGE_FILES = YES; - GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 058969131ABCE06E0059CE2A /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - INFOPLIST_FILE = "Life Without CocoaPods/Info.plist"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = "$(inherited)"; - OTHER_LDFLAGS = ( - "-ObjC", - "-lc++", - ); - PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 058969141ABCE06E0059CE2A /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - INFOPLIST_FILE = "Life Without CocoaPods/Info.plist"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = "$(inherited)"; - OTHER_LDFLAGS = ( - "-ObjC", - "-lc++", - ); - PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; - F729B8CB1D2E176700C9EDBC /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ANALYZER_NONNULL = YES; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_TESTABILITY = YES; - GCC_NO_COMMON_BLOCKS = YES; - INFOPLIST_FILE = "Life With Frameworks/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 9.3; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.Life-With-Frameworks"; - PRODUCT_NAME = "$(TARGET_NAME)"; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - F729B8CC1D2E176700C9EDBC /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ANALYZER_NONNULL = YES; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - GCC_NO_COMMON_BLOCKS = YES; - INFOPLIST_FILE = "Life With Frameworks/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 9.3; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.Life-With-Frameworks"; - PRODUCT_NAME = "$(TARGET_NAME)"; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 058968EA1ABCE06E0059CE2A /* Build configuration list for PBXProject "Life Without CocoaPods" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 058969101ABCE06E0059CE2A /* Debug */, - 058969111ABCE06E0059CE2A /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 058969121ABCE06E0059CE2A /* Build configuration list for PBXNativeTarget "Life Without CocoaPods" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 058969131ABCE06E0059CE2A /* Debug */, - 058969141ABCE06E0059CE2A /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - F729B8D01D2E176700C9EDBC /* Build configuration list for PBXNativeTarget "Life With Frameworks" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - F729B8CB1D2E176700C9EDBC /* Debug */, - F729B8CC1D2E176700C9EDBC /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 058968E71ABCE06E0059CE2A /* Project object */; -} diff --git a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index a9e8c819ee..0000000000 --- a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/xcshareddata/xcschemes/Life Without CocoaPods.xcscheme b/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/xcshareddata/xcschemes/Life Without CocoaPods.xcscheme deleted file mode 100644 index 2abacce743..0000000000 --- a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/xcshareddata/xcschemes/Life Without CocoaPods.xcscheme +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcworkspace/contents.xcworkspacedata b/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index b2fcbec3ec..0000000000 --- a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/AppDelegate.h b/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/AppDelegate.h deleted file mode 100644 index 75aff7fc14..0000000000 --- a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/AppDelegate.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// AppDelegate.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@interface AppDelegate : UIResponder - -@property (strong, nonatomic) UIWindow *window; - - -@end - diff --git a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/AppDelegate.m b/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/AppDelegate.m deleted file mode 100644 index d6890ff2e3..0000000000 --- a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/AppDelegate.m +++ /dev/null @@ -1,24 +0,0 @@ -// -// AppDelegate.m -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "AppDelegate.h" - -#import "ViewController.h" - -@implementation AppDelegate - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions -{ - self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; - self.window.backgroundColor = [UIColor whiteColor]; - self.window.rootViewController = [[ViewController alloc] init]; - [self.window makeKeyAndVisible]; - return YES; -} - -@end diff --git a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/Default-568h@2x.png b/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/Default-568h@2x.png deleted file mode 100644 index 6ee80b9393..0000000000 Binary files a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/Default-568h@2x.png and /dev/null differ diff --git a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/Default-667h@2x.png b/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/Default-667h@2x.png deleted file mode 100644 index e7b975e21b..0000000000 Binary files a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/Default-667h@2x.png and /dev/null differ diff --git a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/Default-736h@3x.png b/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/Default-736h@3x.png deleted file mode 100644 index c8949cae16..0000000000 Binary files a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/Default-736h@3x.png and /dev/null differ diff --git a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/Info.plist b/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/Info.plist deleted file mode 100644 index fb4115c84c..0000000000 --- a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/Info.plist +++ /dev/null @@ -1,36 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/ViewController.h b/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/ViewController.h deleted file mode 100644 index 261e9ff480..0000000000 --- a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/ViewController.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// ViewController.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@interface ViewController : UIViewController - - -@end - diff --git a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/ViewController.m b/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/ViewController.m deleted file mode 100644 index 106ee524f4..0000000000 --- a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/ViewController.m +++ /dev/null @@ -1,28 +0,0 @@ -// -// ViewController.m -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ViewController.h" - -#import - -@interface ViewController () -@property (nonatomic, strong) ASTextNode *textNode; -@end - -@implementation ViewController - -- (void)viewDidLoad -{ - self.textNode = [[ASTextNode alloc] init]; - self.textNode.attributedText = [[NSAttributedString alloc] initWithString:@"Testing, testing." attributes:@{ NSForegroundColorAttributeName: [UIColor redColor] }]; - [self.textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, self.view.bounds.size)]; - self.textNode.frame = (CGRect){ .origin = CGPointZero, .size = self.textNode.calculatedSize }; - [self.view addSubnode:self.textNode]; -} - -@end diff --git a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/main.m b/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/main.m deleted file mode 100644 index ec43dab250..0000000000 --- a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/main.m +++ /dev/null @@ -1,16 +0,0 @@ -// -// main.m -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import "AppDelegate.h" - -int main(int argc, char * argv[]) { - @autoreleasepool { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); - } -} diff --git a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/README.md b/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/README.md deleted file mode 100644 index c20f63a5df..0000000000 --- a/submodules/AsyncDisplayKit/smoke-tests/Life Without CocoaPods/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# "Life Without CocoaPods" - -This is a very simple pseudo-"integration test" project that links against -AsyncDisplayKit manually, rather than using CocoaPods. If it fails to compile, -Travis CI builds will fail. To escape from such dire straits: - -* If you've added a new class intended for public use, make sure you added its - header to the "Public" group of the "Headers" build phase in the - AsyncDisplayKit target. Note that this smoke test will only fail if you - remembered to add your new file to the umbrella helper. - -* If you added a new framework dependency (like AssetsLibrary or Photos), add - it to this project's Link Binary With Libraries build phase and update the - project README (both README.md and docs/index.md). diff --git a/submodules/AsyncDisplayKit/smoke-tests/README.md b/submodules/AsyncDisplayKit/smoke-tests/README.md deleted file mode 100644 index 2c0e607456..0000000000 --- a/submodules/AsyncDisplayKit/smoke-tests/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Integration tests - -See READMEs in subfolders. diff --git a/submodules/AuthTransferUI/BUILD b/submodules/AuthTransferUI/BUILD new file mode 100644 index 0000000000..df8e758fa6 --- /dev/null +++ b/submodules/AuthTransferUI/BUILD @@ -0,0 +1,34 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "AuthTransferUI", + module_name = "AuthTransferUI", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/TelegramCore:TelegramCore", + "//submodules/SyncCore:SyncCore", + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/AccountContext:AccountContext", + "//submodules/QrCode:QrCode", + "//submodules/Camera:Camera", + "//submodules/GlassButtonNode:GlassButtonNode", + "//submodules/AlertUI:AlertUI", + "//submodules/AppBundle:AppBundle", + "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", + "//submodules/OverlayStatusController:OverlayStatusController", + "//submodules/AnimatedStickerNode:AnimatedStickerNode", + "//submodules/Markdown:Markdown", + "//submodules/AnimationUI:AnimationUI", + "//submodules/PresentationDataUtils:PresentationDataUtils", + "//submodules/DeviceAccess:DeviceAccess", + "//submodules/UndoUI:UndoUI", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/AuthorizationUI/BUILD b/submodules/AuthorizationUI/BUILD new file mode 100644 index 0000000000..9e15b1858d --- /dev/null +++ b/submodules/AuthorizationUI/BUILD @@ -0,0 +1,21 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "AuthorizationUI", + module_name = "AuthorizationUI", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/TelegramCore:TelegramCore", + "//submodules/SyncCore:SyncCore", + "//submodules/Display:Display", + "//submodules/TextFormat:TextFormat", + "//submodules/Markdown:Markdown", + "//submodules/TelegramPresentationData:TelegramPresentationData", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/AuthorizationUI/Info.plist b/submodules/AuthorizationUI/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/AuthorizationUI/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/AuthorizationUI/Sources/AuthorizationOptionText.swift b/submodules/AuthorizationUI/Sources/AuthorizationOptionText.swift index 218d8017c9..e9d2cadc8d 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationOptionText.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationOptionText.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import TelegramCore import SyncCore import Display diff --git a/submodules/AuthorizationUI/Sources/AuthorizationUI.h b/submodules/AuthorizationUI/Sources/AuthorizationUI.h deleted file mode 100644 index ff41738826..0000000000 --- a/submodules/AuthorizationUI/Sources/AuthorizationUI.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// AuthorizationUI.h -// AuthorizationUI -// -// Created by Peter on 8/17/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for AuthorizationUI. -FOUNDATION_EXPORT double AuthorizationUIVersionNumber; - -//! Project version string for AuthorizationUI. -FOUNDATION_EXPORT const unsigned char AuthorizationUIVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/AvatarNode/BUILD b/submodules/AvatarNode/BUILD new file mode 100644 index 0000000000..df2c2c6837 --- /dev/null +++ b/submodules/AvatarNode/BUILD @@ -0,0 +1,22 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "AvatarNode", + module_name = "AvatarNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/TelegramCore:TelegramCore", + "//submodules/SyncCore:SyncCore", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/AnimationUI:AnimationUI", + "//submodules/AppBundle:AppBundle", + "//submodules/AccountContext:AccountContext", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/AvatarNode/Info.plist b/submodules/AvatarNode/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/AvatarNode/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/AvatarNode/Sources/AvatarNode.h b/submodules/AvatarNode/Sources/AvatarNode.h deleted file mode 100644 index 999cedcf3d..0000000000 --- a/submodules/AvatarNode/Sources/AvatarNode.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// AvatarNode.h -// AvatarNode -// -// Created by Peter on 8/4/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for AvatarNode. -FOUNDATION_EXPORT double AvatarNodeVersionNumber; - -//! Project version string for AvatarNode. -FOUNDATION_EXPORT const unsigned char AvatarNodeVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/AvatarNode/Sources/AvatarNode.swift b/submodules/AvatarNode/Sources/AvatarNode.swift index 2f4e502e6a..4671db3fb1 100644 --- a/submodules/AvatarNode/Sources/AvatarNode.swift +++ b/submodules/AvatarNode/Sources/AvatarNode.swift @@ -427,7 +427,7 @@ public final class AvatarNode: ASDisplayNode { if let explicitColorIndex = parameters.explicitColorIndex { colorIndex = explicitColorIndex } else { - if let accountPeerId = parameters.accountPeerId, let peerId = parameters.peerId { + if let peerId = parameters.peerId { if peerId.namespace == -1 { colorIndex = -1 } else { @@ -622,3 +622,65 @@ public func drawPeerAvatarLetters(context: CGContext, size: CGSize, font: UIFont context.translateBy(x: -lineOrigin.x, y: -lineOrigin.y) context.textPosition = textPosition } + +public enum AvatarBackgroundColor { + case blue + case yellow + case green + case purple + case red + case violet +} + +public func generateAvatarImage(size: CGSize, icon: UIImage?, color: AvatarBackgroundColor) -> UIImage? { + return generateImage(size, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.beginPath() + context.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height: + size.height)) + context.clip() + + let colorIndex: Int + switch color { + case .blue: + colorIndex = 5 + case .yellow: + colorIndex = 1 + case .green: + colorIndex = 3 + case .purple: + colorIndex = 2 + case .red: + colorIndex = 0 + case .violet: + colorIndex = 6 + } + + let colorsArray: NSArray + if colorIndex == -1 { + colorsArray = grayscaleColors + } else { + colorsArray = AvatarNode.gradientColors[colorIndex % AvatarNode.gradientColors.count] + } + + var locations: [CGFloat] = [1.0, 0.0] + + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradient(colorsSpace: colorSpace, colors: colorsArray, locations: &locations)! + + context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions()) + + context.resetClip() + + context.setBlendMode(.normal) + + context.translateBy(x: size.width / 2.0, y: size.height / 2.0) + context.scaleBy(x: 1.0, y: -1.0) + context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) + + if let icon = icon { + let iconFrame = CGRect(origin: CGPoint(x: floor((size.width - icon.size.width) / 2.0), y: floor((size.height - icon.size.height) / 2.0)), size: icon.size) + context.draw(icon.cgImage!, in: iconFrame) + } + }) +} diff --git a/submodules/BotPaymentsUI/BUILD b/submodules/BotPaymentsUI/BUILD new file mode 100644 index 0000000000..f7213427bf --- /dev/null +++ b/submodules/BotPaymentsUI/BUILD @@ -0,0 +1,29 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "BotPaymentsUI", + module_name = "BotPaymentsUI", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/SyncCore:SyncCore", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/LocalAuth:LocalAuth", + "//submodules/AccountContext:AccountContext", + "//submodules/ItemListUI:ItemListUI", + "//submodules/PasswordSetupUI:PasswordSetupUI", + "//submodules/PhotoResources:PhotoResources", + "//submodules/TelegramNotices:TelegramNotices", + "//submodules/Stripe:Stripe", + "//submodules/CountrySelectionUI:CountrySelectionUI", + "//submodules/AppBundle:AppBundle", + "//submodules/PresentationDataUtils:PresentationDataUtils", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/BotPaymentsUI/Info.plist b/submodules/BotPaymentsUI/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/BotPaymentsUI/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/BotPaymentsUI/Sources/BotPaymentsUI.h b/submodules/BotPaymentsUI/Sources/BotPaymentsUI.h deleted file mode 100644 index 52ac46ad1b..0000000000 --- a/submodules/BotPaymentsUI/Sources/BotPaymentsUI.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// BotPaymentsUI.h -// BotPaymentsUI -// -// Created by Peter on 8/12/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for BotPaymentsUI. -FOUNDATION_EXPORT double BotPaymentsUIVersionNumber; - -//! Project version string for BotPaymentsUI. -FOUNDATION_EXPORT const unsigned char BotPaymentsUIVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/BuildConfig/BUILD b/submodules/BuildConfig/BUILD new file mode 100644 index 0000000000..347afe08eb --- /dev/null +++ b/submodules/BuildConfig/BUILD @@ -0,0 +1,34 @@ +load( + "//build-input/data:variables.bzl", + "telegram_api_id", + "telegram_api_hash", + "telegram_app_center_id", + "telegram_is_internal_build", + "telegram_is_appstore_build", + "telegram_appstore_id", + "telegram_app_specific_url_scheme", +) + +objc_library( + name = "BuildConfig", + module_name = "BuildConfig", + enable_modules = True, + srcs = glob([ + "Sources/*.m", + ]), + copts = [ + "-DAPP_CONFIG_API_ID={}".format(telegram_api_id), + "-DAPP_CONFIG_API_HASH=\\\"{}\\\"".format(telegram_api_hash), + "-DAPP_CONFIG_APP_CENTER_ID=\\\"{}\\\"".format(telegram_app_center_id), + "-DAPP_CONFIG_IS_INTERNAL_BUILD={}".format(telegram_is_internal_build), + "-DAPP_CONFIG_IS_APPSTORE_BUILD={}".format(telegram_is_appstore_build), + "-DAPP_CONFIG_APPSTORE_ID={}".format(telegram_appstore_id), + "-DAPP_SPECIFIC_URL_SCHEME=\\\"{}\\\"".format(telegram_app_specific_url_scheme), + ], + hdrs = glob([ + "Sources/*.h", + ]), + deps = [ + ], + visibility = ["//visibility:public"], +) diff --git a/submodules/BuildConfig/Config-AppStoreLLC.xcconfig b/submodules/BuildConfig/Config-AppStoreLLC.xcconfig deleted file mode 100644 index d6449fc612..0000000000 --- a/submodules/BuildConfig/Config-AppStoreLLC.xcconfig +++ /dev/null @@ -1 +0,0 @@ -#include "../../Telegram-iOS/Config-AppStoreLLC.xcconfig" diff --git a/submodules/BuildConfig/Config-Fork.xcconfig b/submodules/BuildConfig/Config-Fork.xcconfig deleted file mode 100644 index efe0d66c5c..0000000000 --- a/submodules/BuildConfig/Config-Fork.xcconfig +++ /dev/null @@ -1 +0,0 @@ -#include "../../Telegram-iOS/Config-Fork.xcconfig" diff --git a/submodules/BuildConfig/Config-Hockeyapp-Internal.xcconfig b/submodules/BuildConfig/Config-Hockeyapp-Internal.xcconfig deleted file mode 100644 index 8b9e14fa83..0000000000 --- a/submodules/BuildConfig/Config-Hockeyapp-Internal.xcconfig +++ /dev/null @@ -1 +0,0 @@ -#include "../../../Telegram-iOS-Shared/Config/Hockeyapp-Internal/Config.xcconfig" diff --git a/submodules/BuildConfig/Info.plist b/submodules/BuildConfig/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/BuildConfig/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/BuildConfigExtra/BUCK b/submodules/BuildConfigExtra/BUCK index 5d87460156..7c4e20f519 100644 --- a/submodules/BuildConfigExtra/BUCK +++ b/submodules/BuildConfigExtra/BUCK @@ -9,7 +9,7 @@ static_library( "Sources/*.h", ]), exported_headers = glob([ - "Sources/*.h", + "PublicHeaders/**/*.h", ]), deps = [ "//submodules/PKCS:PKCS", diff --git a/submodules/BuildConfigExtra/BUILD b/submodules/BuildConfigExtra/BUILD new file mode 100644 index 0000000000..7e3972e6a0 --- /dev/null +++ b/submodules/BuildConfigExtra/BUILD @@ -0,0 +1,25 @@ + +objc_library( + name = "BuildConfigExtra", + enable_modules = True, + module_name = "BuildConfigExtra", + srcs = glob([ + "Sources/**/*.m", + "Sources/**/*.h", + ]), + hdrs = glob([ + "PublicHeaders/**/*.h", + ]), + includes = [ + "PublicHeaders", + ], + deps = [ + "//submodules/PKCS:PKCS", + ], + sdk_frameworks = [ + "Foundation", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/BuildConfigExtra/Sources/BuildConfigExtra.h b/submodules/BuildConfigExtra/PublicHeaders/BuildConfigExtra/BuildConfigExtra.h similarity index 100% rename from submodules/BuildConfigExtra/Sources/BuildConfigExtra.h rename to submodules/BuildConfigExtra/PublicHeaders/BuildConfigExtra/BuildConfigExtra.h diff --git a/submodules/BuildConfigExtra/Sources/BuildConfigExtra.m b/submodules/BuildConfigExtra/Sources/BuildConfigExtra.m index ddb6a760ca..fcad93dc2a 100644 --- a/submodules/BuildConfigExtra/Sources/BuildConfigExtra.m +++ b/submodules/BuildConfigExtra/Sources/BuildConfigExtra.m @@ -1,4 +1,4 @@ -#import "BuildConfigExtra.h" +#import #include #include diff --git a/submodules/CallListUI/BUILD b/submodules/CallListUI/BUILD new file mode 100644 index 0000000000..7828266e84 --- /dev/null +++ b/submodules/CallListUI/BUILD @@ -0,0 +1,30 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "CallListUI", + module_name = "CallListUI", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/SyncCore:SyncCore", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/TelegramUIPreferences:TelegramUIPreferences", + "//submodules/AccountContext:AccountContext", + "//submodules/ItemListUI:ItemListUI", + "//submodules/AvatarNode:AvatarNode", + "//submodules/TelegramStringFormatting:TelegramStringFormatting", + "//submodules/AlertUI:AlertUI", + "//submodules/PresentationDataUtils:PresentationDataUtils", + "//submodules/TelegramNotices:TelegramNotices", + "//submodules/MergeLists:MergeLists", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/CallListUI/Info.plist b/submodules/CallListUI/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/CallListUI/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/CallListUI/Sources/CallListUI.h b/submodules/CallListUI/Sources/CallListUI.h deleted file mode 100644 index 0104a06195..0000000000 --- a/submodules/CallListUI/Sources/CallListUI.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// CallListUI.h -// CallListUI -// -// Created by Peter on 8/13/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for CallListUI. -FOUNDATION_EXPORT double CallListUIVersionNumber; - -//! Project version string for CallListUI. -FOUNDATION_EXPORT const unsigned char CallListUIVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/Camera/BUILD b/submodules/Camera/BUILD new file mode 100644 index 0000000000..6cced36441 --- /dev/null +++ b/submodules/Camera/BUILD @@ -0,0 +1,17 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "Camera", + module_name = "Camera", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/ChatListSearchItemHeader/BUILD b/submodules/ChatListSearchItemHeader/BUILD new file mode 100644 index 0000000000..edfacc2082 --- /dev/null +++ b/submodules/ChatListSearchItemHeader/BUILD @@ -0,0 +1,17 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatListSearchItemHeader", + module_name = "ChatListSearchItemHeader", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/Display:Display", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/ListSectionHeaderNode:ListSectionHeaderNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/ChatListSearchItemHeader/Info.plist b/submodules/ChatListSearchItemHeader/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/ChatListSearchItemHeader/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/ChatListSearchItemHeader/Sources/ChatListSearchItemHeader.h b/submodules/ChatListSearchItemHeader/Sources/ChatListSearchItemHeader.h deleted file mode 100644 index 4fbda00392..0000000000 --- a/submodules/ChatListSearchItemHeader/Sources/ChatListSearchItemHeader.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// ChatListSearchItemHeader.h -// ChatListSearchItemHeader -// -// Created by Peter on 8/12/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for ChatListSearchItemHeader. -FOUNDATION_EXPORT double ChatListSearchItemHeaderVersionNumber; - -//! Project version string for ChatListSearchItemHeader. -FOUNDATION_EXPORT const unsigned char ChatListSearchItemHeaderVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/ChatListSearchItemHeader/Sources/ChatListSearchItemHeader.swift b/submodules/ChatListSearchItemHeader/Sources/ChatListSearchItemHeader.swift index 19fcbdb221..e90987fbdd 100644 --- a/submodules/ChatListSearchItemHeader/Sources/ChatListSearchItemHeader.swift +++ b/submodules/ChatListSearchItemHeader/Sources/ChatListSearchItemHeader.swift @@ -19,6 +19,8 @@ public enum ChatListSearchItemHeaderType: Int32 { case addToExceptions case mapAddress case nearbyVenues + case chats + case chatTypes } public final class ChatListSearchItemHeader: ListViewItemHeader { @@ -101,6 +103,11 @@ public final class ChatListSearchItemHeaderNode: ListViewItemHeaderNode { self.sectionHeaderNode.title = strings.Map_AddressOnMap.uppercased() case .nearbyVenues: self.sectionHeaderNode.title = strings.Map_PlacesNearby.uppercased() + case .chats: + self.sectionHeaderNode.title = strings.Cache_ByPeerHeader.uppercased() + case .chatTypes: + //TODO:localize + self.sectionHeaderNode.title = "CHAT TYPES" } self.sectionHeaderNode.action = actionTitle @@ -147,6 +154,11 @@ public final class ChatListSearchItemHeaderNode: ListViewItemHeaderNode { self.sectionHeaderNode.title = strings.Map_AddressOnMap.uppercased() case .nearbyVenues: self.sectionHeaderNode.title = strings.Map_PlacesNearby.uppercased() + case .chats: + self.sectionHeaderNode.title = strings.Cache_ByPeerHeader.uppercased() + case .chatTypes: + //TODO:localize + self.sectionHeaderNode.title = "CHAT TYPES" } self.sectionHeaderNode.action = actionTitle diff --git a/submodules/ChatListSearchItemNode/BUILD b/submodules/ChatListSearchItemNode/BUILD new file mode 100644 index 0000000000..c57793411e --- /dev/null +++ b/submodules/ChatListSearchItemNode/BUILD @@ -0,0 +1,20 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatListSearchItemNode", + module_name = "ChatListSearchItemNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/SearchBarNode:SearchBarNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/ChatListSearchItemNode/Info.plist b/submodules/ChatListSearchItemNode/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/ChatListSearchItemNode/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/ChatListSearchItemNode/Sources/ChatListSearchItemNode.h b/submodules/ChatListSearchItemNode/Sources/ChatListSearchItemNode.h deleted file mode 100644 index 74066c502e..0000000000 --- a/submodules/ChatListSearchItemNode/Sources/ChatListSearchItemNode.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// ChatListSearchItemNode.h -// ChatListSearchItemNode -// -// Created by Peter on 8/13/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for ChatListSearchItemNode. -FOUNDATION_EXPORT double ChatListSearchItemNodeVersionNumber; - -//! Project version string for ChatListSearchItemNode. -FOUNDATION_EXPORT const unsigned char ChatListSearchItemNodeVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/ChatListSearchRecentPeersNode/BUILD b/submodules/ChatListSearchRecentPeersNode/BUILD new file mode 100644 index 0000000000..659eec2f7a --- /dev/null +++ b/submodules/ChatListSearchRecentPeersNode/BUILD @@ -0,0 +1,26 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatListSearchRecentPeersNode", + module_name = "ChatListSearchRecentPeersNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/SyncCore:SyncCore", + "//submodules/ListSectionHeaderNode:ListSectionHeaderNode", + "//submodules/HorizontalPeerItem:HorizontalPeerItem", + "//submodules/MergeLists:MergeLists", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/ContextUI:ContextUI", + "//submodules/AccountContext:AccountContext", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/ChatListSearchRecentPeersNode/Info.plist b/submodules/ChatListSearchRecentPeersNode/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/ChatListSearchRecentPeersNode/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/ChatListSearchRecentPeersNode/Sources/ChatListSearchRecentPeersNode.h b/submodules/ChatListSearchRecentPeersNode/Sources/ChatListSearchRecentPeersNode.h deleted file mode 100644 index 82bc0e1c74..0000000000 --- a/submodules/ChatListSearchRecentPeersNode/Sources/ChatListSearchRecentPeersNode.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// ChatListSearchRecentPeersNode.h -// ChatListSearchRecentPeersNode -// -// Created by Peter on 8/5/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for ChatListSearchRecentPeersNode. -FOUNDATION_EXPORT double ChatListSearchRecentPeersNodeVersionNumber; - -//! Project version string for ChatListSearchRecentPeersNode. -FOUNDATION_EXPORT const unsigned char ChatListSearchRecentPeersNodeVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/ChatListUI/BUCK b/submodules/ChatListUI/BUCK index 5dc03a48f1..2694f6a128 100644 --- a/submodules/ChatListUI/BUCK +++ b/submodules/ChatListUI/BUCK @@ -45,6 +45,7 @@ static_library( "//submodules/ItemListPeerActionItem:ItemListPeerActionItem", "//submodules/AnimatedStickerNode:AnimatedStickerNode", "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", + "//submodules/TooltipUI:TooltipUI", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/ChatListUI/BUILD b/submodules/ChatListUI/BUILD new file mode 100644 index 0000000000..0eb8212ebd --- /dev/null +++ b/submodules/ChatListUI/BUILD @@ -0,0 +1,53 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatListUI", + module_name = "ChatListUI", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/SyncCore:SyncCore", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/TelegramUIPreferences:TelegramUIPreferences", + "//submodules/AccountContext:AccountContext", + "//submodules/TelegramBaseController:TelegramBaseController", + "//submodules/OverlayStatusController:OverlayStatusController", + "//submodules/AlertUI:AlertUI", + "//submodules/PresentationDataUtils:PresentationDataUtils", + "//submodules/UndoUI:UndoUI", + "//submodules/TelegramNotices:TelegramNotices", + "//submodules/SearchUI:SearchUI", + "//submodules/MergeLists:MergeLists", + "//submodules/ActivityIndicator:ActivityIndicator", + "//submodules/SearchBarNode:SearchBarNode", + "//submodules/ChatListSearchRecentPeersNode:ChatListSearchRecentPeersNode", + "//submodules/ChatListSearchItemNode:ChatListSearchItemNode", + "//submodules/ChatListSearchItemHeader:ChatListSearchItemHeader", + "//submodules/TemporaryCachedPeerDataManager:TemporaryCachedPeerDataManager", + "//submodules/PeerPresenceStatusManager:PeerPresenceStatusManager", + "//submodules/PeerOnlineMarkerNode:PeerOnlineMarkerNode", + "//submodules/LocalizedPeerData:LocalizedPeerData", + "//submodules/ChatTitleActivityNode:ChatTitleActivityNode", + "//submodules/DeleteChatPeerActionSheetItem:DeleteChatPeerActionSheetItem", + "//submodules/LanguageSuggestionUI:LanguageSuggestionUI", + "//submodules/ContactsPeerItem:ContactsPeerItem", + "//submodules/ContactListUI:ContactListUI", + "//submodules/PhotoResources:PhotoResources", + "//submodules/AppBundle:AppBundle", + "//submodules/ContextUI:ContextUI", + "//submodules/PhoneNumberFormat:PhoneNumberFormat", + "//submodules/TelegramIntents:TelegramIntents", + "//submodules/ItemListPeerActionItem:ItemListPeerActionItem", + "//submodules/AnimatedStickerNode:AnimatedStickerNode", + "//submodules/TooltipUI:TooltipUI", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/ChatListUI/Info.plist b/submodules/ChatListUI/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/ChatListUI/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/ChatListUI/Sources/ChatContextMenus.swift b/submodules/ChatListUI/Sources/ChatContextMenus.swift index 28ca57540d..8894256308 100644 --- a/submodules/ChatListUI/Sources/ChatContextMenus.swift +++ b/submodules/ChatListUI/Sources/ChatContextMenus.swift @@ -18,7 +18,7 @@ func archiveContextMenuItems(context: AccountContext, groupId: PeerGroupId, chat var items: [ContextMenuItem] = [] if !transaction.getUnreadChatListPeerIds(groupId: groupId, filterPredicate: nil).isEmpty { - items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_MarkAllAsRead, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MarkAsRead"), color: theme.contextMenu.primaryColor) }, action: { [weak chatListController] _, f in + items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_MarkAllAsRead, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MarkAsRead"), color: theme.contextMenu.primaryColor) }, action: { _, f in let _ = (context.account.postbox.transaction { transaction in markAllChatsAsReadInteractively(transaction: transaction, viewTracker: context.account.viewTracker, groupId: groupId, filterPredicate: nil) } @@ -40,7 +40,7 @@ func archiveContextMenuItems(context: AccountContext, groupId: PeerGroupId, chat } enum ChatContextMenuSource { - case chatList + case chatList(isFilter: Bool) case search(ChatListSearchContextActionSource) } @@ -146,20 +146,22 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, source: ChatC }))) } - let isPinned = index.pinningIndex != nil - items.append(.action(ContextMenuActionItem(text: isPinned ? strings.ChatList_Context_Unpin : strings.ChatList_Context_Pin, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isPinned ? "Chat/Context Menu/Unpin" : "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { _, f in - let _ = (toggleItemPinned(postbox: context.account.postbox, groupId: group, itemId: .peer(peerId)) - |> deliverOnMainQueue).start(next: { result in - switch result { - case .done: - break - case let .limitExceeded(maxCount): - break - //strongSelf.presentAlert?(strongSelf.currentState.presentationData.strings.DialogList_PinLimitError("\(maxCount)").0) - } - f(.default) - }) - }))) + if case .chatList(false) = source { + let isPinned = index.pinningIndex != nil + items.append(.action(ContextMenuActionItem(text: isPinned ? strings.ChatList_Context_Unpin : strings.ChatList_Context_Pin, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isPinned ? "Chat/Context Menu/Unpin" : "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { _, f in + let _ = (toggleItemPinned(postbox: context.account.postbox, groupId: group, itemId: .peer(peerId)) + |> deliverOnMainQueue).start(next: { result in + switch result { + case .done: + break + case let .limitExceeded(maxCount): + break + //strongSelf.presentAlert?(strongSelf.currentState.presentationData.strings.DialogList_PinLimitError("\(maxCount)").0) + } + f(.default) + }) + }))) + } if !isSavedMessages, let notificationSettings = transaction.getPeerNotificationSettings(peerId) as? TelegramPeerNotificationSettings { var isMuted = false diff --git a/submodules/ChatListUI/Sources/ChatListAdditionalCategoryItem.swift b/submodules/ChatListUI/Sources/ChatListAdditionalCategoryItem.swift new file mode 100644 index 0000000000..a41331f8ba --- /dev/null +++ b/submodules/ChatListUI/Sources/ChatListAdditionalCategoryItem.swift @@ -0,0 +1,327 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import SwiftSignalKit +import ItemListUI +import CheckNode +import AvatarNode +import AccountContext +import TelegramPresentationData +import ChatListSearchItemHeader + +public class ChatListAdditionalCategoryItem: ItemListItem, ListViewItemWithHeader { + let presentationData: ItemListPresentationData + public let sectionId: ItemListSectionId + let context: AccountContext + let title: String + let image: UIImage? + let isSelected: Bool + let action: () -> Void + + public let selectable: Bool = true + + public let header: ListViewItemHeader? + + public init( + presentationData: ItemListPresentationData, + sectionId: ItemListSectionId = 0, + context: AccountContext, + title: String, + image: UIImage?, + isSelected: Bool, + action: @escaping () -> Void + ) { + self.presentationData = presentationData + self.sectionId = sectionId + self.context = context + self.title = title + self.image = image + self.isSelected = isSelected + self.action = action + + self.header = ChatListSearchItemHeader(type: .chatTypes, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) + } + + public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { + async { + let node = ChatListAdditionalCategoryItemNode() + let makeLayout = node.asyncLayout() + let (first, last, firstWithHeader) = ChatListAdditionalCategoryItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem) + let (nodeLayout, nodeApply) = makeLayout(self, params, first, last, firstWithHeader, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) + node.contentSize = nodeLayout.contentSize + node.insets = nodeLayout.insets + + Queue.mainQueue().async { + completion(node, { + let (signal, apply) = nodeApply() + return (signal, { _ in + apply(false, synchronousLoads) + }) + }) + } + } + } + + public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { + Queue.mainQueue().async { + if let nodeValue = node() as? ChatListAdditionalCategoryItemNode { + let layout = nodeValue.asyncLayout() + async { + let (first, last, firstWithHeader) = ChatListAdditionalCategoryItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem) + let (nodeLayout, apply) = layout(self, params, first, last, firstWithHeader, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) + Queue.mainQueue().async { + completion(nodeLayout, { _ in + apply().1(animation.isAnimated, false) + }) + } + } + } + } + } + + public func selected(listView: ListView) { + self.action() + } + + static func mergeType(item: ChatListAdditionalCategoryItem, previousItem: ListViewItem?, nextItem: ListViewItem?) -> (first: Bool, last: Bool, firstWithHeader: Bool) { + var first = false + var last = false + var firstWithHeader = false + if let previousItem = previousItem { + if let header = item.header { + if let previousItem = previousItem as? ListViewItemWithHeader { + firstWithHeader = header.id != previousItem.header?.id + } else { + firstWithHeader = true + } + } + } else { + first = true + firstWithHeader = item.header != nil + } + if let nextItem = nextItem { + if let header = item.header { + if let nextItem = nextItem as? ListViewItemWithHeader { + last = header.id != nextItem.header?.id + } else { + last = true + } + } + } else { + last = true + } + return (first, last, firstWithHeader) + } +} + +private let avatarFont = avatarPlaceholderFont(size: 16.0) + +public class ChatListAdditionalCategoryItemNode: ItemListRevealOptionsItemNode { + private let backgroundNode: ASDisplayNode + private let topSeparatorNode: ASDisplayNode + private let separatorNode: ASDisplayNode + private let highlightedBackgroundNode: ASDisplayNode + + private let avatarNode: ASImageNode + private let titleNode: TextNode + private var selectionNode: CheckNode? + + private var isHighlighted: Bool = false + + private var item: ChatListAdditionalCategoryItem? + + required public init() { + self.backgroundNode = ASDisplayNode() + self.backgroundNode.isLayerBacked = true + + self.topSeparatorNode = ASDisplayNode() + self.topSeparatorNode.isLayerBacked = true + + self.separatorNode = ASDisplayNode() + self.separatorNode.isLayerBacked = true + + self.highlightedBackgroundNode = ASDisplayNode() + self.highlightedBackgroundNode.isLayerBacked = true + + self.avatarNode = ASImageNode() + + self.titleNode = TextNode() + + super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + + self.isAccessibilityElement = true + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.topSeparatorNode) + self.addSubnode(self.separatorNode) + + self.addSubnode(self.avatarNode) + self.addSubnode(self.titleNode) + } + + override public func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { + if let item = self.item { + let (first, last, firstWithHeader) = ChatListAdditionalCategoryItem.mergeType(item: item, previousItem: previousItem, nextItem: nextItem) + let makeLayout = self.asyncLayout() + let (nodeLayout, nodeApply) = makeLayout(item, params, first, last, firstWithHeader, itemListNeighbors(item: item, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) + self.contentSize = nodeLayout.contentSize + self.insets = nodeLayout.insets + let _ = nodeApply() + } + } + + override public func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { + return + + /*super.setHighlighted(highlighted, at: point, animated: animated) + + self.isHighlighted = highlighted + self.updateIsHighlighted(transition: (animated && !highlighted) ? .animated(duration: 0.3, curve: .easeInOut) : .immediate)*/ + } + + + public func updateIsHighlighted(transition: ContainedViewLayoutTransition) { + } + + public func asyncLayout() -> (_ item: ChatListAdditionalCategoryItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool, _ firstWithHeader: Bool, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> (Signal?, (Bool, Bool) -> Void)) { + let makeTitleLayout = TextNode.asyncLayout(self.titleNode) + let currentSelectionNode = self.selectionNode + + let currentItem = self.item + + return { [weak self] item, params, first, last, firstWithHeader, neighbors in + var updatedTheme: PresentationTheme? + + let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize) + + let avatarDiameter: CGFloat = 40.0 + + if currentItem?.presentationData.theme !== item.presentationData.theme { + updatedTheme = item.presentationData.theme + } + let leftInset: CGFloat = 65.0 + params.leftInset + var rightInset: CGFloat = 10.0 + params.rightInset + + let updatedSelectionNode: CheckNode? + let isSelected = item.isSelected + + rightInset += 28.0 + + let selectionNode: CheckNode + if let current = currentSelectionNode { + selectionNode = current + updatedSelectionNode = selectionNode + } else { + selectionNode = CheckNode(strokeColor: item.presentationData.theme.list.itemCheckColors.strokeColor, fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor, style: .plain) + selectionNode.isUserInteractionEnabled = false + updatedSelectionNode = selectionNode + } + + var titleAttributedString: NSAttributedString? + let textColor = item.presentationData.theme.list.itemPrimaryTextColor + titleAttributedString = NSAttributedString(string: item.title, font: titleFont, textColor: textColor) + + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(0.0, params.width - leftInset - rightInset), height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let verticalInset: CGFloat = 13.0 + + let statusHeightComponent: CGFloat + statusHeightComponent = 0.0 + + let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: verticalInset * 2.0 + titleLayout.size.height + statusHeightComponent), insets: UIEdgeInsets(top: firstWithHeader ? 29.0 : 0.0, left: 0.0, bottom: 0.0, right: 0.0)) + + let titleFrame: CGRect + titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((nodeLayout.contentSize.height - titleLayout.size.height) / 2.0)), size: titleLayout.size) + + return (nodeLayout, { [weak self] in + if let strongSelf = self { + return (.complete(), { [weak strongSelf] animated, synchronousLoads in + if let strongSelf = strongSelf { + strongSelf.item = item + + strongSelf.accessibilityLabel = titleAttributedString?.string + + //strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: nodeLayout.contentSize) + //strongSelf.containerNode.isGestureEnabled = item.contextAction != nil + + let transition: ContainedViewLayoutTransition + if animated { + transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) + } else { + transition = .immediate + } + + let revealOffset = strongSelf.revealOffset + + if let _ = updatedTheme { + strongSelf.topSeparatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor + strongSelf.separatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor + strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.plainBackgroundColor + strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor + } + + strongSelf.avatarNode.image = item.image + + strongSelf.topSeparatorNode.isHidden = true + + transition.updateFrame(node: strongSelf.avatarNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset - 50.0, y: floor((nodeLayout.contentSize.height - avatarDiameter) / 2.0)), size: CGSize(width: avatarDiameter, height: avatarDiameter))) + + let _ = titleApply() + transition.updateFrame(node: strongSelf.titleNode, frame: titleFrame.offsetBy(dx: revealOffset, dy: 0.0)) + + if let updatedSelectionNode = updatedSelectionNode { + if strongSelf.selectionNode !== updatedSelectionNode { + strongSelf.selectionNode?.removeFromSupernode() + strongSelf.selectionNode = updatedSelectionNode + strongSelf.addSubnode(updatedSelectionNode) + } + updatedSelectionNode.setIsChecked(isSelected, animated: animated) + + updatedSelectionNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 32.0 - 12.0, y: floor((nodeLayout.contentSize.height - 32.0) / 2.0)), size: CGSize(width: 32.0, height: 32.0)) + } else if let selectionNode = strongSelf.selectionNode { + selectionNode.removeFromSupernode() + strongSelf.selectionNode = nil + } + + let separatorHeight = UIScreenPixel + + let topHighlightInset: CGFloat = (first || !nodeLayout.insets.top.isZero) ? 0.0 : separatorHeight + strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: nodeLayout.contentSize.width, height: nodeLayout.contentSize.height)) + strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: nodeLayout.size.width, height: nodeLayout.size.height + topHighlightInset)) + strongSelf.topSeparatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(nodeLayout.insets.top, separatorHeight)), size: CGSize(width: nodeLayout.contentSize.width, height: separatorHeight)) + strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: max(0.0, nodeLayout.size.width - leftInset), height: separatorHeight)) + strongSelf.separatorNode.isHidden = last + + strongSelf.updateLayout(size: nodeLayout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset) + } + }) + } else { + return (nil, { _, _ in + }) + } + }) + } + } + + override public func layoutHeaderAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { + let bounds = self.bounds + accessoryItemNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -29.0), size: CGSize(width: bounds.size.width, height: 29.0)) + } + + override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.5) + } + + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration * 0.5, removeOnCompletion: false) + } + + override public func header() -> ListViewItemHeader? { + if let item = self.item { + return item.header + } else { + return nil + } + } +} diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 751772e64b..48731695cb 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -2,6 +2,7 @@ import Foundation import UIKit import Postbox import SwiftSignalKit +import AsyncDisplayKit import Display import TelegramCore import SyncCore @@ -21,21 +22,20 @@ import ContextUI import AppBundle import LocalizedPeerData import TelegramIntents +import TooltipUI private func fixListNodeScrolling(_ listNode: ListView, searchNode: NavigationBarSearchContentNode) -> Bool { + if listNode.scroller.isDragging { + return false + } if searchNode.expansionProgress > 0.0 && searchNode.expansionProgress < 1.0 { - let scrollToItem: ListViewScrollToItem - let targetProgress: CGFloat + let offset: CGFloat if searchNode.expansionProgress < 0.6 { - scrollToItem = ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: true, curve: .Default(duration: nil), directionHint: .Up) - targetProgress = 0.0 + offset = navigationBarSearchContentHeight } else { - scrollToItem = ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up) - targetProgress = 1.0 + offset = 0.0 } - searchNode.updateExpansionProgress(targetProgress, animated: true) - - listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: scrollToItem, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + let _ = listNode.scrollToOffsetFromTop(offset) return true } else if searchNode.expansionProgress == 1.0 { var sortItemNode: ListViewItemNode? @@ -120,6 +120,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, private var badgeDisposable: Disposable? private var badgeIconDisposable: Disposable? + private var didAppear = false private var dismissSearchOnDisappear = false private var passcodeLockTooltipDisposable = MetaDisposable() @@ -133,18 +134,20 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, private var presentationDataDisposable: Disposable? private let stateDisposable = MetaDisposable() - private var filterDisposable: Disposable? + private let filterDisposable = MetaDisposable() + private let featuredFiltersDisposable = MetaDisposable() + private var processedFeaturedFilters = false + + private let isReorderingTabsValue = ValuePromise(false) private var searchContentNode: NavigationBarSearchContentNode? private let tabContainerNode: ChatListFilterTabContainerNode - private var tabContainerData: ([ChatListFilterTabEntry], ChatListFilterTabEntryId)? - - private let chatListFilterValue = Promise() + private var tabContainerData: [ChatListFilterTabEntry]? public override func updateNavigationCustomData(_ data: Any?, progress: CGFloat, transition: ContainedViewLayoutTransition) { if self.isNodeLoaded { - self.chatListDisplayNode.chatListNode.updateSelectedChatLocation(data as? ChatLocation, progress: progress, transition: transition) + self.chatListDisplayNode.containerNode.updateSelectedChatLocation(data: data as? ChatLocation, progress: progress, transition: transition) } } @@ -166,13 +169,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .always, locationBroadcastPanelSource: .summary) - self.hasTabBarItemContextAction = true + self.tabBarItemContextActionType = .whenActive self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style let title: String if let filter = self.filter { - title = filter.title ?? "" + title = filter.title } else if self.groupId == .root { title = self.presentationData.strings.DialogList_Title self.navigationBar?.item = nil @@ -232,37 +235,24 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, if strongSelf.chatListDisplayNode.searchDisplayController != nil { strongSelf.deactivateSearch(animated: true) } else { - switch strongSelf.chatListDisplayNode.chatListNode.visibleContentOffset() { + switch strongSelf.chatListDisplayNode.containerNode.currentItemNode.visibleContentOffset() { case .none, .unknown: if let searchContentNode = strongSelf.searchContentNode { searchContentNode.updateExpansionProgress(1.0, animated: true) } - strongSelf.chatListDisplayNode.chatListNode.scrollToPosition(.top) + strongSelf.chatListDisplayNode.containerNode.currentItemNode.scrollToPosition(.top) case let .known(offset): - if offset <= navigationBarSearchContentHeight + 1.0 { + if offset <= navigationBarSearchContentHeight + 1.0 && strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter != nil { strongSelf.tabContainerNode.tabSelected?(.all) } else { if let searchContentNode = strongSelf.searchContentNode { searchContentNode.updateExpansionProgress(1.0, animated: true) } - strongSelf.chatListDisplayNode.chatListNode.scrollToPosition(.top) + strongSelf.chatListDisplayNode.containerNode.currentItemNode.scrollToPosition(.top) } } } } - self.longTapWithTabBar = { [weak self] in - guard let strongSelf = self else { - return - } - if strongSelf.chatListDisplayNode.searchDisplayController != nil { - strongSelf.deactivateSearch(animated: true) - } else { - if let searchContentNode = strongSelf.searchContentNode { - searchContentNode.updateExpansionProgress(1.0, animated: true) - } - strongSelf.chatListDisplayNode.chatListNode.scrollToPosition(.auto) - } - } let hasProxy = context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.proxySettings]) |> map { sharedData -> (Bool, Bool) in @@ -287,36 +277,72 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, context.account.networkState, hasProxy, passcode, - self.chatListDisplayNode.chatListNode.state - ).start(next: { [weak self] networkState, proxy, passcode, state in + self.chatListDisplayNode.containerNode.currentItemState, + self.isReorderingTabsValue.get() + ).start(next: { [weak self] networkState, proxy, passcode, state, isReorderingTabs in if let strongSelf = self { let defaultTitle: String if strongSelf.groupId == .root { - if let chatListFilter = strongSelf.filter { - let title: String = chatListFilter.title ?? strongSelf.presentationData.strings.DialogList_Title - defaultTitle = title - } else { - defaultTitle = strongSelf.presentationData.strings.DialogList_Title - } + defaultTitle = strongSelf.presentationData.strings.DialogList_Title } else { defaultTitle = strongSelf.presentationData.strings.ChatList_ArchivedChatsTitle } if state.editing { - if strongSelf.groupId == .root && strongSelf.filter == nil { + if strongSelf.groupId == .root { strongSelf.navigationItem.rightBarButtonItem = nil } let title = !state.selectedPeerIds.isEmpty ? strongSelf.presentationData.strings.ChatList_SelectedChats(Int32(state.selectedPeerIds.count)) : defaultTitle strongSelf.titleView.title = NetworkStatusTitle(text: title, activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false) + } else if isReorderingTabs { + if strongSelf.groupId == .root { + strongSelf.navigationItem.rightBarButtonItem = nil + } + let leftBarButtonItem = UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Done, style: .done, target: strongSelf, action: #selector(strongSelf.reorderingDonePressed)) + strongSelf.navigationItem.leftBarButtonItem = leftBarButtonItem + + let (_, connectsViaProxy) = proxy + switch networkState { + case .waitingForNetwork: + strongSelf.titleView.title = NetworkStatusTitle(text: strongSelf.presentationData.strings.State_WaitingForNetwork, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false) + case let .connecting(proxy): + var text = strongSelf.presentationData.strings.State_Connecting + if let layout = strongSelf.validLayout, proxy != nil && layout.metrics.widthClass != .regular && layout.size.width > 320.0 { + text = strongSelf.presentationData.strings.State_ConnectingToProxy + } + strongSelf.titleView.title = NetworkStatusTitle(text: text, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false) + case .updating: + strongSelf.titleView.title = NetworkStatusTitle(text: strongSelf.presentationData.strings.State_Updating, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false) + case .online: + strongSelf.titleView.title = NetworkStatusTitle(text: defaultTitle, activity: false, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false) + } } else { var isRoot = false if case .root = strongSelf.groupId { isRoot = true - if strongSelf.filter == nil { + + if isReorderingTabs { + strongSelf.navigationItem.rightBarButtonItem = nil + } else { let rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationComposeIcon(strongSelf.presentationData.theme), style: .plain, target: strongSelf, action: #selector(strongSelf.composePressed)) rightBarButtonItem.accessibilityLabel = strongSelf.presentationData.strings.VoiceOver_Navigation_Compose strongSelf.navigationItem.rightBarButtonItem = rightBarButtonItem } + + if isReorderingTabs { + let leftBarButtonItem = UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Done, style: .done, target: strongSelf, action: #selector(strongSelf.reorderingDonePressed)) + strongSelf.navigationItem.leftBarButtonItem = leftBarButtonItem + } else { + let editItem: UIBarButtonItem + if state.editing { + editItem = UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(strongSelf.donePressed)) + editItem.accessibilityLabel = strongSelf.presentationData.strings.Common_Done + } else { + editItem = UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(strongSelf.editPressed)) + editItem.accessibilityLabel = strongSelf.presentationData.strings.Common_Edit + } + strongSelf.navigationItem.leftBarButtonItem = editItem + } } let (hasProxy, connectsViaProxy) = proxy @@ -419,85 +445,34 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, } } - if self.filter == nil { - let preferencesKey: PostboxViewKey = .preferences(keys: Set([ - ApplicationSpecificPreferencesKeys.chatListFilterSettings - ])) - let filterItems = chatListFilterItems(context: context) - |> map { totalCount, items -> [ChatListFilterTabEntry] in - var result: [ChatListFilterTabEntry] = [] - result.append(.all(unreadCount: totalCount)) - for (filter, unreadCount) in items { - result.append(.filter(id: filter.id, text: filter.title ?? "", unreadCount: unreadCount)) - } - return result - } - |> distinctUntilChanged - self.filterDisposable = (combineLatest(queue: .mainQueue(), - context.account.postbox.combinedView(keys: [ - preferencesKey - ]), - filterItems, - self.chatListFilterValue.get() |> map { $0?.id } |> distinctUntilChanged - ) - |> deliverOnMainQueue).start(next: { [weak self] combinedView, filterItems, selectedFilter in + if self.filter == nil, case .root = self.groupId { + self.chatListDisplayNode.containerNode.currentItemFilterUpdated = { [weak self] filter, fraction, transition, force in guard let strongSelf = self else { return } - var filterSettings: ChatListFilterSettings = .default - if let preferencesView = combinedView.views[preferencesKey] as? PreferencesView { - if let value = preferencesView.values[ApplicationSpecificPreferencesKeys.chatListFilterSettings] as? ChatListFilterSettings { - filterSettings = value - } + guard let layout = strongSelf.validLayout else { + return } - - var resolvedItems = filterItems - if !filterSettings.displayTabs || groupId != .root { - resolvedItems = [] + guard let tabContainerData = strongSelf.tabContainerData else { + return } - - var wasEmpty = false - if let tabContainerData = strongSelf.tabContainerData { - wasEmpty = tabContainerData.0.count <= 1 - } else { - wasEmpty = true + if force { + strongSelf.tabContainerNode.cancelAnimations() } - let selectedEntryId: ChatListFilterTabEntryId = selectedFilter.flatMap { .filter($0) } ?? .all - strongSelf.tabContainerData = (resolvedItems, selectedEntryId) - - let isEmpty = resolvedItems.count <= 1 - - if wasEmpty != isEmpty { - strongSelf.navigationBar?.setSecondaryContentNode(isEmpty ? nil : strongSelf.tabContainerNode) - if let parentController = strongSelf.parent as? TabBarController { - parentController.navigationBar?.setSecondaryContentNode(isEmpty ? nil : strongSelf.tabContainerNode) - } - } - - if let layout = strongSelf.validLayout { - if wasEmpty != isEmpty { - strongSelf.containerLayoutUpdated(layout, transition: .immediate) - (strongSelf.parent as? TabBarController)?.updateLayout() - } else { - strongSelf.tabContainerNode.update(size: CGSize(width: layout.size.width, height: NavigationBar.defaultSecondaryContentHeight), sideInset: layout.safeInsets.left, filters: resolvedItems, selectedFilter: selectedEntryId, presentationData: strongSelf.presentationData, transition: .animated(duration: 0.4, curve: .spring)) - } - } - }) + strongSelf.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: tabContainerData, selectedFilter: filter, isReordering: strongSelf.chatListDisplayNode.isReorderingFilters || strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, isEditing: false, transitionFraction: fraction, presentationData: strongSelf.presentationData, transition: transition) + } + self.reloadFilters() } self.tabContainerNode.tabSelected = { [weak self] id in guard let strongSelf = self else { return } - let _ = (strongSelf.context.account.postbox.transaction { transaction -> [ChatListFilter] in - let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default - return settings.filters - } + let _ = (currentChatListFilters(postbox: strongSelf.context.account.postbox) |> deliverOnMainQueue).start(next: { [weak self] filters in guard let strongSelf = self else { return } - let previousFilter = strongSelf.chatListDisplayNode.chatListNode.chatListFilter let updatedFilter: ChatListFilter? switch id { case .all: @@ -518,29 +493,16 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, updatedFilter = nil } } - if previousFilter?.id != updatedFilter?.id { - var paneSwitchAnimationDirection: ChatListNodePaneSwitchAnimationDirection? - if let previousId = previousFilter?.id, let updatedId = updatedFilter?.id, let previousIndex = filters.index(where: { $0.id == previousId }), let updatedIndex = filters.index(where: { $0.id == updatedId }) { - if previousIndex > updatedIndex { - paneSwitchAnimationDirection = .right - } else { - paneSwitchAnimationDirection = .left - } - } else if (previousFilter != nil) != (updatedFilter != nil) { - if previousFilter != nil { - paneSwitchAnimationDirection = .right - } else { - paneSwitchAnimationDirection = .left - } - } - if let direction = paneSwitchAnimationDirection { - strongSelf.chatListDisplayNode.chatListNode.paneSwitchAnimation = (direction, .animated(duration: 0.4, curve: .spring)) - } - } - strongSelf.chatListDisplayNode.chatListNode.updateFilter(updatedFilter) + strongSelf.chatListDisplayNode.containerNode.switchToFilter(id: updatedFilter.flatMap { .filter($0.id) } ?? .all) }) } + self.tabContainerNode.tabRequestedDeletion = { [weak self] id in + if case let .filter(id) = id { + self?.askForFilterRemoval(id: id) + } + } + self.tabContainerNode.addFilter = { [weak self] in self?.openFilterSettings() } @@ -549,47 +511,21 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, guard let strongSelf = self else { return } - var items: [ContextMenuItem] = [] - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Common_Edit, icon: { _ in - return nil - }, action: { c, f in - c.dismiss(completion: { - guard let strongSelf = self else { - return - } - let _ = (strongSelf.context.account.postbox.transaction { transaction -> [ChatListFilter] in - let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default - return settings.filters - } - |> deliverOnMainQueue).start(next: { presetList in - guard let strongSelf = self else { - return - } - var found = false - for filter in presetList { - if filter.id == id { - strongSelf.push(chatListFilterPresetController(context: strongSelf.context, currentPreset: filter, updated: { _ in })) - f(.dismissWithoutContent) - found = true - break - } - } - }) - }) - }))) - if let chatListFilter = strongSelf.chatListDisplayNode.chatListNode.chatListFilter, chatListFilter.includePeers.count < 100 { + let _ = (currentChatListFilters(postbox: strongSelf.context.account.postbox) + |> deliverOnMainQueue).start(next: { [weak self] filters in + guard let strongSelf = self else { + return + } + var items: [ContextMenuItem] = [] //TODO:localization - items.append(.action(ContextMenuActionItem(text: "Add Chats", icon: { _ in - return nil + items.append(.action(ContextMenuActionItem(text: "Edit Folder", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { c, f in c.dismiss(completion: { guard let strongSelf = self else { return } - let _ = (strongSelf.context.account.postbox.transaction { transaction -> [ChatListFilter] in - let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default - return settings.filters - } + let _ = (currentChatListFilters(postbox: strongSelf.context.account.postbox) |> deliverOnMainQueue).start(next: { presetList in guard let strongSelf = self else { return @@ -597,19 +533,84 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, var found = false for filter in presetList { if filter.id == id { - strongSelf.push(chatListFilterAddChatsController(context: strongSelf.context, filter: filter)) + strongSelf.push(chatListFilterPresetController(context: strongSelf.context, currentPreset: filter, updated: { _ in })) f(.dismissWithoutContent) found = true break } } + if !found { + f(.default) + } }) }) }))) - } - - let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatListHeaderBarContextExtractedContentSource(controller: strongSelf, sourceNode: sourceNode)), items: .single(items), reactionItems: [], recognizer: nil, gesture: gesture) - strongSelf.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller) + if let filter = filters.first(where: { $0.id == id }), filter.data.includePeers.count < 100 { + //TODO:localization + items.append(.action(ContextMenuActionItem(text: "Add Chats", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor) + }, action: { c, f in + c.dismiss(completion: { + guard let strongSelf = self else { + return + } + let _ = (currentChatListFilters(postbox: strongSelf.context.account.postbox) + |> deliverOnMainQueue).start(next: { presetList in + guard let strongSelf = self else { + return + } + var found = false + for filter in presetList { + if filter.id == id { + strongSelf.push(chatListFilterAddChatsController(context: strongSelf.context, filter: filter)) + f(.dismissWithoutContent) + found = true + break + } + } + if !found { + f(.default) + } + }) + }) + }))) + //TODO:localize + items.append(.action(ContextMenuActionItem(text: "Remove", textColor: .destructive, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) + }, action: { c, f in + c.dismiss(completion: { + guard let strongSelf = self else { + return + } + strongSelf.askForFilterRemoval(id: id) + }) + }))) + + if filters.count > 1 { + items.append(.separator) + items.append(.action(ContextMenuActionItem(text: "Reorder Tabs", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) + }, action: { c, f in + //f(.default) + c.dismiss(completion: { + guard let strongSelf = self else { + return + } + + strongSelf.chatListDisplayNode.isReorderingFilters = true + strongSelf.isReorderingTabsValue.set(true) + strongSelf.searchContentNode?.setIsEnabled(false, animated: true) + if let layout = strongSelf.validLayout { + strongSelf.updateLayout(layout: layout, transition: .animated(duration: 0.2, curve: .easeInOut)) + } + }) + }))) + } + } + + let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatListHeaderBarContextExtractedContentSource(controller: strongSelf, sourceNode: sourceNode)), items: .single(items), reactionItems: [], recognizer: nil, gesture: gesture) + strongSelf.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller) + }) } } @@ -626,7 +627,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, self.suggestLocalizationDisposable.dispose() self.presentationDataDisposable?.dispose() self.stateDisposable.dispose() - self.filterDisposable?.dispose() + self.filterDisposable.dispose() + self.featuredFiltersDisposable.dispose() } private func updateThemeAndStrings() { @@ -642,11 +644,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, } self.searchContentNode?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: self.presentationData.strings.DialogList_SearchLabel) - var editing = false - self.chatListDisplayNode.chatListNode.updateState { state in - editing = state.editing - return state - } + let editing = self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing let editItem: UIBarButtonItem if editing { editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed)) @@ -669,6 +667,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) + if let layout = self.validLayout { + self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, isEditing: false, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .immediate) + } + if self.isNodeLoaded { self.chatListDisplayNode.updatePresentationData(self.presentationData) } @@ -676,7 +678,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, override public func loadDisplayNode() { self.displayNode = ChatListControllerNode(context: self.context, groupId: self.groupId, filter: self.filter, previewing: self.previewing, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, controller: self) - self.chatListFilterValue.set(self.chatListDisplayNode.chatListNode.appliedChatListFilterSignal) self.chatListDisplayNode.navigationBar = self.navigationBar @@ -684,37 +685,37 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, self?.deactivateSearch(animated: true) } - self.chatListDisplayNode.chatListNode.activateSearch = { [weak self] in + self.chatListDisplayNode.containerNode.activateSearch = { [weak self] in self?.activateSearch() } - self.chatListDisplayNode.chatListNode.presentAlert = { [weak self] text in + self.chatListDisplayNode.containerNode.presentAlert = { [weak self] text in if let strongSelf = self { self?.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) } } - self.chatListDisplayNode.chatListNode.present = { [weak self] c in + self.chatListDisplayNode.containerNode.present = { [weak self] c in if let strongSelf = self { - self?.present(c, in: .window(.root)) + strongSelf.present(c, in: .window(.root)) } } - self.chatListDisplayNode.chatListNode.toggleArchivedFolderHiddenByDefault = { [weak self] in + self.chatListDisplayNode.containerNode.toggleArchivedFolderHiddenByDefault = { [weak self] in guard let strongSelf = self else { return } strongSelf.toggleArchivedFolderHiddenByDefault() } - self.chatListDisplayNode.chatListNode.deletePeerChat = { [weak self] peerId in + self.chatListDisplayNode.containerNode.deletePeerChat = { [weak self] peerId in guard let strongSelf = self else { return } strongSelf.deletePeerChat(peerId: peerId) } - self.chatListDisplayNode.chatListNode.peerSelected = { [weak self] peerId, animated, isAd in + self.chatListDisplayNode.containerNode.peerSelected = { [weak self] peer, animated, isAd in if let strongSelf = self { if let navigationController = strongSelf.navigationController as? NavigationController { if isAd { @@ -738,37 +739,37 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, scrollToEndIfExists = true } - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), scrollToEndIfExists: scrollToEndIfExists, options: strongSelf.groupId == PeerGroupId.root ? [.removeOnMasterDetails] : [], parentGroupId: strongSelf.groupId, completion: { [weak self] in - self?.chatListDisplayNode.chatListNode.clearHighlightAnimated(true) + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer.id), scrollToEndIfExists: scrollToEndIfExists, options: strongSelf.groupId == PeerGroupId.root ? [.removeOnMasterDetails] : [], parentGroupId: strongSelf.groupId, completion: { [weak self] in + self?.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true) })) } } } - self.chatListDisplayNode.chatListNode.groupSelected = { [weak self] groupId in + self.chatListDisplayNode.containerNode.groupSelected = { [weak self] groupId in if let strongSelf = self { if let navigationController = strongSelf.navigationController as? NavigationController { let chatListController = ChatListControllerImpl(context: strongSelf.context, groupId: groupId, controlsHistoryPreload: false, enableDebugActions: false) chatListController.navigationPresentation = .master navigationController.pushViewController(chatListController) - strongSelf.chatListDisplayNode.chatListNode.clearHighlightAnimated(true) + strongSelf.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true) } } } - self.chatListDisplayNode.chatListNode.updatePeerGrouping = { [weak self] peerId, group in + self.chatListDisplayNode.containerNode.updatePeerGrouping = { [weak self] peerId, group in guard let strongSelf = self else { return } if group { strongSelf.archiveChats(peerIds: [peerId]) } else { - strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId) + strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(peerId) let _ = updatePeerGroupIdInteractively(postbox: strongSelf.context.account.postbox, peerId: peerId, groupId: group ? Namespaces.PeerGroup.archive : .root).start(completed: { guard let strongSelf = self else { return } - strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil) + strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(nil) }) } } @@ -786,7 +787,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(actualPeerId), subject: .message(messageId), purposefulAction: { self?.deactivateSearch(animated: false) }, scrollToEndIfExists: scrollToEndIfExists, options: strongSelf.groupId == PeerGroupId.root ? [.removeOnMasterDetails] : [])) - strongSelf.chatListDisplayNode.chatListNode.clearHighlightAnimated(true) + strongSelf.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true) } } })) @@ -815,7 +816,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer.id), purposefulAction: { [weak self] in self?.deactivateSearch(animated: false) }, scrollToEndIfExists: scrollToEndIfExists, options: strongSelf.groupId == PeerGroupId.root ? [.removeOnMasterDetails] : [])) - strongSelf.chatListDisplayNode.chatListNode.clearHighlightAnimated(true) + strongSelf.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true) } } })) @@ -860,14 +861,17 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, } } - self.chatListDisplayNode.dismissSelf = { [weak self] in + self.chatListDisplayNode.dismissSelfIfCompletedPresentation = { [weak self] in guard let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController else { return } + if !strongSelf.didAppear { + return + } navigationController.filterController(strongSelf, animated: true) } - self.chatListDisplayNode.chatListNode.contentOffsetChanged = { [weak self] offset in + self.chatListDisplayNode.containerNode.contentOffsetChanged = { [weak self] offset in if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode, let validLayout = strongSelf.validLayout { var offset = offset if validLayout.inVoiceOver { @@ -877,7 +881,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, } } - self.chatListDisplayNode.chatListNode.contentScrollingEnded = { [weak self] listView in + self.chatListDisplayNode.containerNode.contentScrollingEnded = { [weak self] listView in if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode { return fixListNodeScrolling(listView, searchNode: searchContentNode) } else { @@ -885,19 +889,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, } } - self.chatListDisplayNode.isEmptyUpdated = { [weak self] isEmpty in - if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode, let _ = strongSelf.validLayout { - if isEmpty { - //searchContentNode.updateListVisibleContentOffset(.known(0.0)) - } - } - } - self.chatListDisplayNode.emptyListAction = { [weak self] in guard let strongSelf = self else { return } - if let filter = strongSelf.chatListDisplayNode.chatListNode.chatListFilter { + if let filter = strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter { strongSelf.push(chatListFilterPresetController(context: strongSelf.context, currentPreset: filter, updated: { _ in })) } else { strongSelf.composePressed() @@ -908,7 +904,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, self?.toolbarActionSelected(action: action) } - self.chatListDisplayNode.chatListNode.activateChatPreview = { [weak self] item, node, gesture in + self.chatListDisplayNode.containerNode.activateChatPreview = { [weak self] item, node, gesture in guard let strongSelf = self else { gesture?.cancel() return @@ -922,7 +918,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, case let .peer(peer): let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(peer.peer.peerId), subject: nil, botStart: nil, mode: .standard(previewing: true)) chatController.canReadHistory.set(false) - let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: chatContextMenuItems(context: strongSelf.context, peerId: peer.peer.peerId, source: .chatList, chatListController: strongSelf), reactionItems: [], gesture: gesture) + let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: chatContextMenuItems(context: strongSelf.context, peerId: peer.peer.peerId, source: .chatList(isFilter: strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter != nil), chatListController: strongSelf), reactionItems: [], gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) } } @@ -940,7 +936,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, } let context = self.context - let peerIdsAndOptions: Signal<(ChatListSelectionOptions, Set)?, NoError> = self.chatListDisplayNode.chatListNode.state + let peerIdsAndOptions: Signal<(ChatListSelectionOptions, Set)?, NoError> = self.chatListDisplayNode.containerNode.currentItemState |> map { state -> Set? in if !state.editing { return nil @@ -988,7 +984,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, } } } - toolbar = Toolbar(leftAction: leftAction, rightAction: ToolbarAction(title: presentationData.strings.Common_Delete, isEnabled: options.delete), middleAction: strongSelf.chatListDisplayNode.chatListNode.chatListFilter != nil ? nil : ToolbarAction(title: presentationData.strings.ChatList_ArchiveAction, isEnabled: archiveEnabled)) + toolbar = Toolbar(leftAction: leftAction, rightAction: ToolbarAction(title: presentationData.strings.Common_Delete, isEnabled: options.delete), middleAction: strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter != nil ? nil : ToolbarAction(title: presentationData.strings.ChatList_ArchiveAction, isEnabled: archiveEnabled)) } } else { if let (options, peerIds) = peerIdsAndOptions { @@ -1006,7 +1002,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, strongSelf.setToolbar(toolbar, transition: .animated(duration: 0.3, curve: .easeInOut)) })) - self.ready.set(self.chatListDisplayNode.chatListNode.ready) + self.ready.set(self.chatListDisplayNode.containerNode.ready) self.displayNodeDidLoad() } @@ -1014,6 +1010,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, override public func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) + self.didAppear = true + + self.chatListDisplayNode.containerNode.updateEnableAdjacentFilterLoading(true) + guard case .root = self.groupId else { return } @@ -1105,7 +1105,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, })) } - self.chatListDisplayNode.chatListNode.addedVisibleChatsWithPeerIds = { [weak self] peerIds in + self.chatListDisplayNode.containerNode.addedVisibleChatsWithPeerIds = { [weak self] peerIds in guard let strongSelf = self else { return } @@ -1124,17 +1124,71 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, return true }) } + + if !self.processedFeaturedFilters { + self.featuredFiltersDisposable.set(( + self.context.account.postbox.transaction { transaction -> ChatListFiltersFeaturedState? in + return transaction.getPreferencesEntry(key: PreferencesKeys.chatListFiltersFeaturedState) as? ChatListFiltersFeaturedState + } + |> delay(1.0, queue: .mainQueue()) + |> deliverOnMainQueue + ).start(next: { [weak self] featuredState in + guard let strongSelf = self, let featuredState = featuredState else { + return + } + + let _ = (currentChatListFilters(postbox: strongSelf.context.account.postbox) + |> deliverOnMainQueue).start(next: { filters in + guard let strongSelf = self else { + return + } + strongSelf.processedFeaturedFilters = true + if !featuredState.isSeen && !featuredState.filters.isEmpty && filters.isEmpty { + let _ = (currentChatListFilters(postbox: strongSelf.context.account.postbox) + |> deliverOnMainQueue).start(next: { filters in + guard let strongSelf = self else { + return + } + let hasFilters = !filters.isEmpty + if let _ = strongSelf.validLayout, let parentController = strongSelf.parent as? TabBarController, let sourceFrame = parentController.frameForControllerTab(controller: strongSelf) { + let absoluteFrame = sourceFrame + //TODO:localize + let text: String + if hasFilters { + text = "Hold on 'Chats' to edit folders and switch between views." + } else { + text = "Hold to organize your chats with folders." + } + parentController.present(TooltipScreen(text: text, location: CGPoint(x: absoluteFrame.midX - 14.0, y: absoluteFrame.minY - 8.0), shouldDismissOnTouch: { point in + guard let strongSelf = self, let parentController = strongSelf.parent as? TabBarController else { + return true + } + if parentController.isPointInsideContentArea(point: point) { + return false + } + return true + }), in: .current) + } + }) + } + }) + })) + } } override public func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) + self.chatListDisplayNode.containerNode.updateEnableAdjacentFilterLoading(false) + self.forEachController({ controller in if let controller = controller as? UndoOverlayController { controller.dismissWithCommitAction() } return true }) + + self.featuredFiltersDisposable.set(nil) } override public func viewDidDisappear(_ animated: Bool) { @@ -1145,7 +1199,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, self.deactivateSearch(animated: false) } - self.chatListDisplayNode.chatListNode.clearHighlightAnimated(true) + self.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true) } override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { @@ -1155,19 +1209,23 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, self.validLayout = layout - var tabContainerOffset: CGFloat = 0.0 - if !self.displayNavigationBar { - tabContainerOffset += layout.statusBarHeight ?? 0.0 - tabContainerOffset += 44.0 + 44.0 + 44.0 - } - - transition.updateFrame(node: self.tabContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: self.visualNavigationInsetHeight - self.additionalHeight - NavigationBar.defaultSecondaryContentHeight + tabContainerOffset), size: CGSize(width: layout.size.width, height: NavigationBar.defaultSecondaryContentHeight))) - self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: NavigationBar.defaultSecondaryContentHeight), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.tabContainerData?.1, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring)) + self.updateLayout(layout: layout, transition: transition) if let searchContentNode = self.searchContentNode, layout.inVoiceOver != wasInVoiceOver { searchContentNode.updateListVisibleContentOffset(.known(0.0)) - self.chatListDisplayNode.chatListNode.scrollToPosition(.top) + self.chatListDisplayNode.scrollToTop() } + } + + private func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + var tabContainerOffset: CGFloat = 0.0 + if !self.displayNavigationBar { + tabContainerOffset += layout.statusBarHeight ?? 0.0 + tabContainerOffset += 44.0 + 20.0 + } + + transition.updateFrame(node: self.tabContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: self.visualNavigationInsetHeight - self.additionalHeight - 46.0 + tabContainerOffset), size: CGSize(width: layout.size.width, height: 46.0))) + self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, isEditing: false, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring)) self.chatListDisplayNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationInsetHeight, visualNavigationHeight: self.visualNavigationInsetHeight, cleanNavigationBarHeight: self.cleanNavigationHeight, transition: transition) } @@ -1188,15 +1246,21 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, } self.searchContentNode?.setIsEnabled(false, animated: true) - self.chatListDisplayNode.chatListNode.updateState { state in + self.chatListDisplayNode.containerNode.updateState { state in var state = state state.editing = true state.peerIdWithRevealedOptions = nil return state } + self.chatListDisplayNode.isEditing = true + if let layout = self.validLayout { + self.updateLayout(layout: layout, transition: .animated(duration: 0.2, curve: .easeInOut)) + } } @objc private func donePressed() { + self.reorderingDonePressed() + let editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed)) editItem.accessibilityLabel = self.presentationData.strings.Common_Edit if case .root = self.groupId, self.filter == nil { @@ -1206,18 +1270,200 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, } (self.navigationController as? NavigationController)?.updateMasterDetailsBlackout(nil, transition: .animated(duration: 0.4, curve: .spring)) self.searchContentNode?.setIsEnabled(true, animated: true) - self.chatListDisplayNode.chatListNode.updateState { state in + self.chatListDisplayNode.containerNode.updateState { state in var state = state state.editing = false state.peerIdWithRevealedOptions = nil state.selectedPeerIds.removeAll() return state } + self.chatListDisplayNode.isEditing = false + if let layout = self.validLayout { + self.updateLayout(layout: layout, transition: .animated(duration: 0.2, curve: .easeInOut)) + } + } + + @objc private func reorderingDonePressed() { + if let reorderedFilterIds = self.tabContainerNode.reorderedFilterIds { + let _ = (updateChatListFiltersInteractively(postbox: self.context.account.postbox, { stateFilters in + var updatedFilters: [ChatListFilter] = [] + for id in reorderedFilterIds { + if let index = stateFilters.firstIndex(where: { $0.id == id }) { + updatedFilters.append(stateFilters[index]) + } + } + updatedFilters.append(contentsOf: stateFilters.compactMap { filter -> ChatListFilter? in + if !updatedFilters.contains(where: { $0.id == filter.id }) { + return filter + } else { + return nil + } + }) + return updatedFilters + }) + |> deliverOnMainQueue).start(completed: { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.reloadFilters(firstUpdate: { + guard let strongSelf = self else { + return + } + strongSelf.chatListDisplayNode.isReorderingFilters = false + strongSelf.isReorderingTabsValue.set(false) + strongSelf.searchContentNode?.setIsEnabled(true, animated: true) + if let layout = strongSelf.validLayout { + strongSelf.updateLayout(layout: layout, transition: .animated(duration: 0.2, curve: .easeInOut)) + } + }) + }) + } + } + + private func reloadFilters(firstUpdate: (() -> Void)? = nil) { + let preferencesKey: PostboxViewKey = .preferences(keys: Set([ + ApplicationSpecificPreferencesKeys.chatListFilterSettings + ])) + let filterItems = chatListFilterItems(context: self.context) + var notifiedFirstUpdate = false + self.filterDisposable.set((combineLatest(queue: .mainQueue(), + context.account.postbox.combinedView(keys: [ + preferencesKey + ]), + filterItems + ) + |> deliverOnMainQueue).start(next: { [weak self] _, countAndFilterItems in + guard let strongSelf = self else { + return + } + let (_, items) = countAndFilterItems + var filterItems: [ChatListFilterTabEntry] = [] + filterItems.append(.all(unreadCount: 0)) + for (filter, unreadCount) in items { + filterItems.append(.filter(id: filter.id, text: filter.title, unreadCount: unreadCount)) + } + + var resolvedItems = filterItems + if strongSelf.groupId != .root { + resolvedItems = [] + } + + var wasEmpty = false + if let tabContainerData = strongSelf.tabContainerData { + wasEmpty = tabContainerData.count <= 1 + } else { + wasEmpty = true + } + var selectedEntryId = strongSelf.chatListDisplayNode.containerNode.currentItemFilter + var resetCurrentEntry = false + if !resolvedItems.contains(where: { $0.id == selectedEntryId }) { + resetCurrentEntry = true + if let tabContainerData = strongSelf.tabContainerData { + var found = false + if let index = tabContainerData.firstIndex(where: { $0.id == selectedEntryId }) { + for i in (0 ..< index - 1).reversed() { + if resolvedItems.contains(where: { $0.id == tabContainerData[i].id }) { + selectedEntryId = tabContainerData[i].id + found = true + break + } + } + } + if !found { + selectedEntryId = .all + } + } else { + selectedEntryId = .all + } + } + strongSelf.tabContainerData = resolvedItems + var availableFilters: [ChatListContainerNodeFilter] = [] + availableFilters.append(.all) + for item in items { + availableFilters.append(.filter(item.0)) + } + strongSelf.chatListDisplayNode.containerNode.updateAvailableFilters(availableFilters) + + let isEmpty = resolvedItems.count <= 1 + + if wasEmpty != isEmpty { + strongSelf.navigationBar?.setSecondaryContentNode(isEmpty ? nil : strongSelf.tabContainerNode) + if let parentController = strongSelf.parent as? TabBarController { + parentController.navigationBar?.setSecondaryContentNode(isEmpty ? nil : strongSelf.tabContainerNode) + } + } + + if let layout = strongSelf.validLayout { + if wasEmpty != isEmpty { + strongSelf.containerLayoutUpdated(layout, transition: .immediate) + (strongSelf.parent as? TabBarController)?.updateLayout() + } else { + strongSelf.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: resolvedItems, selectedFilter: selectedEntryId, isReordering: strongSelf.chatListDisplayNode.isReorderingFilters || strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, isEditing: false, transitionFraction: strongSelf.chatListDisplayNode.containerNode.transitionFraction, presentationData: strongSelf.presentationData, transition: .animated(duration: 0.4, curve: .spring)) + } + } + + if !notifiedFirstUpdate { + notifiedFirstUpdate = true + firstUpdate?() + } + + if resetCurrentEntry { + strongSelf.tabContainerNode.tabSelected?(selectedEntryId) + } + })) + } + + private func askForFilterRemoval(id: Int32) { + let actionSheet = ActionSheetController(presentationData: self.presentationData) + + //TODO:localization + actionSheet.setItemGroups([ + ActionSheetItemGroup(items: [ + ActionSheetTextItem(title: "This will remove the filter, your chats will not be deleted."), + ActionSheetButtonItem(title: "Remove", color: .destructive, action: { [weak self, weak actionSheet] in + actionSheet?.dismissAnimated() + + guard let strongSelf = self else { + return + } + + let commit: () -> Void = { + guard let strongSelf = self else { + return + } + + if strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter?.id == id { + if strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing { + strongSelf.donePressed() + } + } + + let _ = updateChatListFiltersInteractively(postbox: strongSelf.context.account.postbox, { filters in + return filters.filter({ $0.id != id }) + }).start() + } + + if strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter?.id == id { + strongSelf.chatListDisplayNode.containerNode.switchToFilter(id: .all, completion: { + commit() + }) + } else { + commit() + } + }) + ]), + ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ]) + ]) + self.present(actionSheet, in: .window(.root)) } public func activateSearch() { if self.displayNavigationBar { - let _ = (self.chatListDisplayNode.chatListNode.contentsReady + let _ = (self.chatListDisplayNode.containerNode.currentItemNode.contentsReady |> take(1) |> deliverOnMainQueue).start(completed: { [weak self] in guard let strongSelf = self else { @@ -1303,10 +1549,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, return nil } - let listLocation = self.view.convert(location, to: self.chatListDisplayNode.chatListNode.view) + let listLocation = self.view.convert(location, to: self.chatListDisplayNode.containerNode.currentItemNode.view) var selectedNode: ChatListItemNode? - self.chatListDisplayNode.chatListNode.forEachItemNode { itemNode in + self.chatListDisplayNode.containerNode.currentItemNode.forEachItemNode { itemNode in if let itemNode = itemNode as? ChatListItemNode, itemNode.frame.contains(listLocation), !itemNode.isDisplayingRevealedOptions { selectedNode = itemNode } @@ -1346,12 +1592,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, chatController.updatePresentationMode(.standard(previewing: false)) if let navigationController = self.navigationController as? NavigationController { self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, chatController: chatController, context: self.context, chatLocation: chatController.chatLocation, animated: false)) - self.chatListDisplayNode.chatListNode.clearHighlightAnimated(true) + self.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true) } } else if let chatListController = viewControllerToCommit as? ChatListController { if let navigationController = self.navigationController as? NavigationController { navigationController.pushViewController(chatListController, animated: false, completion: {}) - self.chatListDisplayNode.chatListNode.clearHighlightAnimated(true) + self.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true) } } } @@ -1373,22 +1619,22 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, let inputShortcuts: [KeyShortcut] = [ KeyShortcut(title: strings.KeyCommand_JumpToPreviousChat, input: UIKeyCommand.inputUpArrow, modifiers: [.alternate], action: { [weak self] in if let strongSelf = self { - strongSelf.chatListDisplayNode.chatListNode.selectChat(.previous(unread: false)) + strongSelf.chatListDisplayNode.containerNode.currentItemNode.selectChat(.previous(unread: false)) } }), KeyShortcut(title: strings.KeyCommand_JumpToNextChat, input: UIKeyCommand.inputDownArrow, modifiers: [.alternate], action: { [weak self] in if let strongSelf = self { - strongSelf.chatListDisplayNode.chatListNode.selectChat(.next(unread: false)) + strongSelf.chatListDisplayNode.containerNode.currentItemNode.selectChat(.next(unread: false)) } }), KeyShortcut(title: strings.KeyCommand_JumpToPreviousUnreadChat, input: UIKeyCommand.inputUpArrow, modifiers: [.alternate, .shift], action: { [weak self] in if let strongSelf = self { - strongSelf.chatListDisplayNode.chatListNode.selectChat(.previous(unread: true)) + strongSelf.chatListDisplayNode.containerNode.currentItemNode.selectChat(.previous(unread: true)) } }), KeyShortcut(title: strings.KeyCommand_JumpToNextUnreadChat, input: UIKeyCommand.inputDownArrow, modifiers: [.alternate, .shift], action: { [weak self] in if let strongSelf = self { - strongSelf.chatListDisplayNode.chatListNode.selectChat(.next(unread: true)) + strongSelf.chatListDisplayNode.containerNode.currentItemNode.selectChat(.next(unread: true)) } }), KeyShortcut(title: strings.KeyCommand_NewMessage, input: "N", modifiers: [.command], action: { [weak self] in @@ -1403,9 +1649,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, let openChat: (Int) -> Void = { [weak self] index in if let strongSelf = self { if index == 0 { - strongSelf.chatListDisplayNode.chatListNode.selectChat(.peerId(strongSelf.context.account.peerId)) + strongSelf.chatListDisplayNode.containerNode.currentItemNode.selectChat(.peerId(strongSelf.context.account.peerId)) } else { - strongSelf.chatListDisplayNode.chatListNode.selectChat(.index(index - 1)) + strongSelf.chatListDisplayNode.containerNode.currentItemNode.selectChat(.index(index - 1)) } } } @@ -1420,11 +1666,16 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, } override public func toolbarActionSelected(action: ToolbarActionOption) { - let peerIds = self.chatListDisplayNode.chatListNode.currentState.selectedPeerIds + let peerIds = self.chatListDisplayNode.containerNode.currentItemNode.currentState.selectedPeerIds if case .left = action { let signal: Signal + var completion: (() -> Void)? let context = self.context if !peerIds.isEmpty { + self.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(peerIds.first!) + completion = { [weak self] in + self?.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(nil) + } signal = self.context.account.postbox.transaction { transaction -> Void in for peerId in peerIds { togglePeerUnreadMarkInteractively(transaction: transaction, viewTracker: context.account.viewTracker, peerId: peerId, setToValue: false) @@ -1433,12 +1684,19 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, } else { let groupId = self.groupId signal = self.context.account.postbox.transaction { transaction -> Void in - markAllChatsAsReadInteractively(transaction: transaction, viewTracker: context.account.viewTracker, groupId: groupId, filterPredicate: self.chatListDisplayNode.chatListNode.chatListFilter.flatMap(chatListFilterPredicate)) + let filterPredicate = (self.chatListDisplayNode.containerNode.currentItemNode.chatListFilter?.data).flatMap(chatListFilterPredicate) + markAllChatsAsReadInteractively(transaction: transaction, viewTracker: context.account.viewTracker, groupId: groupId, filterPredicate: filterPredicate) + if let filterPredicate = filterPredicate { + for additionalGroupId in filterPredicate.includeAdditionalPeerGroupIds { + markAllChatsAsReadInteractively(transaction: transaction, viewTracker: context.account.viewTracker, groupId: additionalGroupId, filterPredicate: filterPredicate) + } + } } } let _ = (signal |> deliverOnMainQueue).start(completed: { [weak self] in self?.donePressed() + completion?() }) } else if case .right = action, !peerIds.isEmpty { let actionSheet = ActionSheetController(presentationData: self.presentationData) @@ -1450,7 +1708,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, return } - strongSelf.chatListDisplayNode.chatListNode.updateState({ state in + strongSelf.chatListDisplayNode.containerNode.updateState({ state in var state = state for peerId in peerIds { state.pendingRemovalPeerIds.insert(peerId) @@ -1494,15 +1752,15 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, |> deliverOnMainQueue).start() return true } else if value == .undo { - strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerIds.first!) - strongSelf.chatListDisplayNode.chatListNode.updateState({ state in + strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(peerIds.first!) + strongSelf.chatListDisplayNode.containerNode.updateState({ state in var state = state for peerId in peerIds { state.pendingRemovalPeerIds.remove(peerId) } return state }) - self?.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerIds.first!) + self?.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(peerIds.first!) return true } return false @@ -1526,7 +1784,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, self.archiveChats(peerIds: Array(peerIds)) } else { if !peerIds.isEmpty { - self.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerIds.first!) + self.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(peerIds.first!) let _ = (self.context.account.postbox.transaction { transaction -> Void in for peerId in peerIds { updatePeerGroupIdInteractively(transaction: transaction, peerId: peerId, groupId: .root) @@ -1536,7 +1794,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, guard let strongSelf = self else { return } - strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil) + strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(nil) strongSelf.donePressed() }) } @@ -1559,7 +1817,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, guard let strongSelf = self else { return } - strongSelf.chatListDisplayNode.chatListNode.updateState { state in + strongSelf.chatListDisplayNode.containerNode.updateState { state in var state = state if value { state.archiveShouldBeTemporaryRevealed = false @@ -1677,7 +1935,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, guard let strongSelf = self else { return } - strongSelf.chatListDisplayNode.chatListNode.updateState({ state in + strongSelf.chatListDisplayNode.containerNode.updateState({ state in var state = state state.pendingClearHistoryPeerIds.insert(peer.peerId) return state @@ -1698,7 +1956,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, guard let strongSelf = self else { return } - strongSelf.chatListDisplayNode.chatListNode.updateState({ state in + strongSelf.chatListDisplayNode.containerNode.updateState({ state in var state = state state.pendingClearHistoryPeerIds.remove(peer.peerId) return state @@ -1706,7 +1964,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, }) return true } else if value == .undo { - strongSelf.chatListDisplayNode.chatListNode.updateState({ state in + strongSelf.chatListDisplayNode.containerNode.updateState({ state in var state = state state.pendingClearHistoryPeerIds.remove(peer.peerId) return state @@ -1870,7 +2128,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, return } let postbox = self.context.account.postbox - self.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerIds[0]) + self.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(peerIds[0]) let _ = (ApplicationSpecificNotice.incrementArchiveChatTips(accountManager: self.context.sharedContext.accountManager, count: 1) |> deliverOnMainQueue).start(next: { [weak self] previousHintCount in let _ = (postbox.transaction { transaction -> Void in @@ -1882,7 +2140,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, guard let strongSelf = self else { return } - strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil) + strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(nil) for peerId in peerIds { deleteSendMessageIntents(peerId: peerId) @@ -1893,7 +2151,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, return false } if value == .undo { - strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerIds[0]) + strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(peerIds[0]) let _ = (postbox.transaction { transaction -> Void in for peerId in peerIds { updatePeerGroupIdInteractively(transaction: transaction, peerId: peerId, groupId: .root) @@ -1903,7 +2161,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, guard let strongSelf = self else { return } - strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil) + strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(nil) }) return true } else { @@ -1949,13 +2207,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, } let peerId = peer.peerId - self.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId) - self.chatListDisplayNode.chatListNode.updateState({ state in + self.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(peerId) + self.chatListDisplayNode.containerNode.updateState({ state in var state = state state.pendingRemovalPeerIds.insert(peer.peerId) return state }) - self.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil) + self.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(nil) let statusText: String if let channel = chatPeer as? TelegramChannel { if deleteGloballyIfPossible { @@ -1999,7 +2257,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, return false } if value == .commit { - strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId) + strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(peerId) if let channel = chatPeer as? TelegramChannel { strongSelf.context.peerChannelMemberCategoriesContextsManager.externallyRemoved(peerId: channel.id, memberId: strongSelf.context.account.peerId) } @@ -2007,25 +2265,25 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, guard let strongSelf = self else { return } - strongSelf.chatListDisplayNode.chatListNode.updateState({ state in + strongSelf.chatListDisplayNode.containerNode.updateState({ state in var state = state state.pendingRemovalPeerIds.remove(peer.peerId) return state }) - strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil) + strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(nil) deleteSendMessageIntents(peerId: peerId) }) completion() return true } else if value == .undo { - strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId) - strongSelf.chatListDisplayNode.chatListNode.updateState({ state in + strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(peerId) + strongSelf.chatListDisplayNode.containerNode.updateState({ state in var state = state state.pendingRemovalPeerIds.remove(peer.peerId) return state }) - strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil) + strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(nil) return true } return false @@ -2050,45 +2308,15 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, } private func openFilterSettings() { - self.push(chatListFilterPresetListController(context: self.context, updated: { _ in + self.chatListDisplayNode.containerNode.updateEnableAdjacentFilterLoading(false) + self.push(chatListFilterPresetListController(context: self.context, mode: .modal, dismissed: { [weak self] in + self?.chatListDisplayNode.containerNode.updateEnableAdjacentFilterLoading(true) })) } - public func presentTabBarPreviewingController(sourceNodes: [ASDisplayNode]) { - if self.isNodeLoaded { - let _ = (self.context.account.postbox.transaction { transaction -> [ChatListFilter] in - let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default - return settings.filters - } - |> deliverOnMainQueue).start(next: { [weak self] presetList in - guard let strongSelf = self else { - return - } - let controller = TabBarChatListFilterController(context: strongSelf.context, sourceNodes: sourceNodes, presetList: presetList, currentPreset: strongSelf.chatListDisplayNode.chatListNode.chatListFilter, setup: { - guard let strongSelf = self else { - return - } - strongSelf.push(chatListFilterPresetListController(context: strongSelf.context, updated: { _ in - })) - }, updatePreset: { value in - guard let strongSelf = self else { - return - } - if let value = value { - strongSelf.tabContainerNode.tabSelected?(.filter(value.id)) - } - }) - strongSelf.context.sharedContext.mainWindow?.present(controller, on: .root) - }) - } - } - override public func tabBarItemContextAction(sourceNode: ContextExtractedContentContainingNode, gesture: ContextGesture) { let _ = (combineLatest(queue: .mainQueue(), - self.context.account.postbox.transaction { transaction -> [ChatListFilter] in - let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default - return settings.filters - }, + currentChatListFilters(postbox: self.context.account.postbox), chatListFilterItems(context: self.context) |> take(1) ) @@ -2097,78 +2325,46 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, return } + let _ = markChatListFeaturedFiltersAsSeen(postbox: strongSelf.context.account.postbox).start() + let (_, filterItems) = filterItemsAndTotalCount var items: [ContextMenuItem] = [] - items.append(.action(ContextMenuActionItem(text: presetList.isEmpty ? "Add Filter" : "Edit Filters", icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor) + items.append(.action(ContextMenuActionItem(text: presetList.isEmpty ? "Add Folder" : "Edit Folders", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: presetList.isEmpty ? "Chat/Context Menu/Add" : "Chat/Context Menu/ItemList"), color: theme.contextMenu.primaryColor) }, action: { c, f in c.dismiss(completion: { guard let strongSelf = self else { return } - if presetList.isEmpty { - if let navigationController = strongSelf.navigationController as? NavigationController { - var viewControllers = navigationController.viewControllers - //viewControllers.append(chatListFilterPresetListController(context: strongSelf.context, updated: { _ in })) - viewControllers.append(chatListFilterPresetController(context: strongSelf.context, currentPreset: nil, updated: { _ in })) - navigationController.setViewControllers(viewControllers, animated: true) - } - } else { - strongSelf.push(chatListFilterPresetListController(context: strongSelf.context, updated: { _ in })) - } + strongSelf.openFilterSettings() }) }))) + if strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter != nil { + items.append(.action(ContextMenuActionItem(text: "All Chats", icon: { theme in + return nil + }, action: { c, f in + f(.dismissWithoutContent) + guard let strongSelf = self else { + return + } + strongSelf.tabContainerNode.tabSelected?(.all) + }))) + } + if !presetList.isEmpty { items.append(.separator) for preset in presetList { - enum ChatListFilterType { - case generic - case unmuted - case unread - case channels - case groups - case bots - case secretChats - case privateChats - } - let filterType: ChatListFilterType - if preset.includePeers.isEmpty { - if preset.categories == .all { - if preset.excludeRead { - filterType = .unread - } else if preset.excludeMuted { - filterType = .unmuted - } else { - filterType = .generic - } - } else { - if preset.categories == .channels { - filterType = .channels - } else if preset.categories.isSubset(of: [.publicGroups, .privateGroups]) { - filterType = .groups - } else if preset.categories == .bots { - filterType = .bots - } else if preset.categories == .secretChats { - filterType = .secretChats - } else if preset.categories == .privateChats { - filterType = .privateChats - } else { - filterType = .generic - } - } - } else { - filterType = .generic - } + let filterType = chatListFilterType(preset) var badge = "" for item in filterItems { if item.0.id == preset.id && item.1 != 0 { badge = "\(item.1)" } } - items.append(.action(ContextMenuActionItem(text: preset.title ?? "", badge: badge, icon: { theme in + items.append(.action(ContextMenuActionItem(text: preset.title, badge: badge, icon: { theme in let imageName: String switch filterType { case .generic: @@ -2183,10 +2379,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, imageName = "Chat/Context Menu/Groups" case .bots: imageName = "Chat/Context Menu/Bots" - case .secretChats: - imageName = "Chat/Context Menu/Timer" - case .privateChats: + case .contacts: imageName = "Chat/Context Menu/User" + case .nonContacts: + imageName = "Chat/Context Menu/UnknownUser" } return generateTintedImage(image: UIImage(bundleImageName: imageName), color: theme.contextMenu.primaryColor) }, action: { _, f in @@ -2203,10 +2399,32 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, strongSelf.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller) }) } + + override public func tabBarItemSwipeAction(direction: TabBarItemSwipeDirection) { + guard let entries = self.tabContainerData, var index = entries.firstIndex(where: { $0.id == self.chatListDisplayNode.containerNode.currentItemFilter }) else { + return + } + switch direction { + case .right: + if index == 0 { + index = entries.count - 1 + } else { + index -= 1 + } + case .left: + if index == entries.count - 1 { + index = 0 + } else { + index += 1 + } + } + self.tabContainerNode.tabSelected?(entries[index].id) + } } private final class ChatListTabBarContextExtractedContentSource: ContextExtractedContentSource { let keepInPlace: Bool = true + let ignoreContentTouches: Bool = true private let controller: ChatListController private let sourceNode: ContextExtractedContentContainingNode @@ -2227,6 +2445,7 @@ private final class ChatListTabBarContextExtractedContentSource: ContextExtracte private final class ChatListHeaderBarContextExtractedContentSource: ContextExtractedContentSource { let keepInPlace: Bool = false + let ignoreContentTouches: Bool = true private let controller: ChatListController private let sourceNode: ContextExtractedContentContainingNode diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 4464bf8cfc..bdde633318 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -26,19 +26,906 @@ private final class ChatListControllerNodeView: UITracingLayerView, PreviewingHo weak var controller: ChatListControllerImpl? } -private struct TestItem: Comparable, Identifiable { - var value: Int - var version: Int +enum ChatListContainerNodeFilter: Equatable { + case all + case filter(ChatListFilter) - var stableId: Int { - return self.value + var id: ChatListFilterTabEntryId { + switch self { + case .all: + return .all + case let .filter(filter): + return .filter(filter.id) + } } - static func <(lhs: TestItem, rhs: TestItem) -> Bool { - if lhs.version != rhs.version { - return lhs.version < rhs.version + var filter: ChatListFilter? { + switch self { + case .all: + return nil + case let .filter(filter): + return filter + } + } +} + +private final class ShimmerEffectNode: ASDisplayNode { + private var currentBackgroundColor: UIColor? + private var currentForegroundColor: UIColor? + private let imageNodeContainer: ASDisplayNode + private let imageNode: ASImageNode + + private var absoluteLocation: (CGRect, CGSize)? + private var isCurrentlyInHierarchy = false + private var shouldBeAnimating = false + + override init() { + self.imageNodeContainer = ASDisplayNode() + self.imageNodeContainer.isLayerBacked = true + + self.imageNode = ASImageNode() + self.imageNode.isLayerBacked = true + self.imageNode.displaysAsynchronously = false + self.imageNode.displayWithoutProcessing = true + self.imageNode.contentMode = .scaleToFill + + super.init() + + self.isLayerBacked = true + self.clipsToBounds = true + + self.imageNodeContainer.addSubnode(self.imageNode) + self.addSubnode(self.imageNodeContainer) + } + + override func didEnterHierarchy() { + super.didEnterHierarchy() + + self.isCurrentlyInHierarchy = true + self.updateAnimation() + } + + override func didExitHierarchy() { + super.didExitHierarchy() + + self.isCurrentlyInHierarchy = false + self.updateAnimation() + } + + func update(backgroundColor: UIColor, foregroundColor: UIColor) { + if let currentBackgroundColor = self.currentBackgroundColor, currentBackgroundColor.isEqual(backgroundColor), let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.isEqual(foregroundColor) { + return + } + self.currentBackgroundColor = backgroundColor + self.currentForegroundColor = foregroundColor + + self.imageNode.image = generateImage(CGSize(width: 4.0, height: 320.0), opaque: true, scale: 1.0, rotatedContext: { size, context in + context.setFillColor(backgroundColor.cgColor) + context.fill(CGRect(origin: CGPoint(), size: size)) + + context.clip(to: CGRect(origin: CGPoint(), size: size)) + + let transparentColor = foregroundColor.withAlphaComponent(0.0).cgColor + let peakColor = foregroundColor.cgColor + + var locations: [CGFloat] = [0.0, 0.5, 1.0] + let colors: [CGColor] = [transparentColor, peakColor, transparentColor] + + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)! + + context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions()) + }) + } + + func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + if let absoluteLocation = self.absoluteLocation, absoluteLocation.0 == rect && absoluteLocation.1 == containerSize { + return + } + let sizeUpdated = self.absoluteLocation?.1 != containerSize + let frameUpdated = self.absoluteLocation?.0 != rect + self.absoluteLocation = (rect, containerSize) + + if sizeUpdated { + if self.shouldBeAnimating { + self.imageNode.layer.removeAnimation(forKey: "shimmer") + self.addImageAnimation() + } + } + + if frameUpdated { + self.imageNodeContainer.frame = CGRect(origin: CGPoint(x: -rect.minX, y: -rect.minY), size: containerSize) + } + + self.updateAnimation() + } + + private func updateAnimation() { + let shouldBeAnimating = self.isCurrentlyInHierarchy && self.absoluteLocation != nil + if shouldBeAnimating != self.shouldBeAnimating { + self.shouldBeAnimating = shouldBeAnimating + if shouldBeAnimating { + self.addImageAnimation() + } else { + self.imageNode.layer.removeAnimation(forKey: "shimmer") + } + } + } + + private func addImageAnimation() { + guard let containerSize = self.absoluteLocation?.1 else { + return + } + let gradientHeight: CGFloat = 250.0 + self.imageNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -gradientHeight), size: CGSize(width: containerSize.width, height: gradientHeight)) + let animation = self.imageNode.layer.makeAnimation(from: 0.0 as NSNumber, to: (containerSize.height + gradientHeight) as NSNumber, keyPath: "position.y", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 1.3 * 1.0, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true) + animation.repeatCount = Float.infinity + animation.beginTime = 1.0 + self.imageNode.layer.add(animation, forKey: "shimmer") + } +} + +private final class ChatListShimmerNode: ASDisplayNode { + private let backgroundColorNode: ASDisplayNode + private let effectNode: ShimmerEffectNode + private let maskNode: ASImageNode + private var currentParams: (size: CGSize, presentationData: PresentationData)? + + override init() { + self.backgroundColorNode = ASDisplayNode() + self.effectNode = ShimmerEffectNode() + self.maskNode = ASImageNode() + + super.init() + + self.isUserInteractionEnabled = false + + self.addSubnode(self.backgroundColorNode) + self.addSubnode(self.effectNode) + self.addSubnode(self.maskNode) + } + + func update(context: AccountContext, size: CGSize, presentationData: PresentationData, transition: ContainedViewLayoutTransition) { + if self.currentParams?.size != size || self.currentParams?.presentationData !== presentationData { + self.currentParams = (size, presentationData) + + let chatListPresentationData = ChatListPresentationData(theme: presentationData.theme, fontSize: presentationData.chatFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true) + + let peer1 = TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: 1), accessHash: nil, firstName: "FirstName", lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) + let timestamp1: Int32 = 100000 + let peers = SimpleDictionary() + let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, additionalCategorySelected: { _ in + }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, activateChatPreview: { _, _, gesture in + gesture?.cancel() + }, present: { _ in }) + + let items = (0 ..< 2).map { _ -> ChatListItem in + return ChatListItem(presentationData: chatListPresentationData, context: context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer1, text: "Text", attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []), peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + } + + var itemNodes: [ChatListItemNode] = [] + for i in 0 ..< items.count { + items[i].nodeConfiguredForParams(async: { f in f() }, params: ListViewItemLayoutParams(width: size.width, leftInset: 0.0, rightInset: 0.0, availableHeight: 100.0), synchronousLoads: false, previousItem: i == 0 ? nil : items[i - 1], nextItem: (i == items.count - 1) ? nil : items[i + 1], completion: { node, apply in + if let itemNode = node as? ChatListItemNode { + itemNodes.append(itemNode) + } + apply().1(ListViewItemApply(isOnScreen: true)) + }) + } + + self.backgroundColorNode.backgroundColor = presentationData.theme.list.mediaPlaceholderColor + + self.maskNode.image = generateImage(size, rotatedContext: { size, context in + context.setFillColor(presentationData.theme.chatList.backgroundColor.cgColor) + context.fill(CGRect(origin: CGPoint(), size: size)) + + var currentY: CGFloat = 0.0 + let fakeLabelPlaceholderHeight: CGFloat = 8.0 + + func fillLabelPlaceholderRect(origin: CGPoint, width: CGFloat) { + let startPoint = origin + let diameter = fakeLabelPlaceholderHeight + context.fillEllipse(in: CGRect(origin: startPoint, size: CGSize(width: diameter, height: diameter))) + context.fillEllipse(in: CGRect(origin: CGPoint(x: startPoint.x + width - diameter, y: startPoint.y), size: CGSize(width: diameter, height: diameter))) + context.fill(CGRect(origin: CGPoint(x: startPoint.x + diameter / 2.0, y: startPoint.y), size: CGSize(width: width - diameter, height: diameter))) + } + + while currentY < size.height { + let sampleIndex = 0 + let itemHeight: CGFloat = itemNodes[sampleIndex].contentSize.height + + context.setBlendMode(.copy) + context.setFillColor(UIColor.clear.cgColor) + + context.fillEllipse(in: itemNodes[sampleIndex].avatarNode.frame.offsetBy(dx: 0.0, dy: currentY)) + let titleFrame = itemNodes[sampleIndex].titleNode.frame.offsetBy(dx: 0.0, dy: currentY) + fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: floor(titleFrame.midY - fakeLabelPlaceholderHeight / 2.0)), width: 60.0) + + fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: currentY + itemHeight - floor(itemNodes[sampleIndex].titleNode.frame.midY - fakeLabelPlaceholderHeight / 2.0) - fakeLabelPlaceholderHeight), width: 60.0) + + fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: currentY + floor((itemHeight - fakeLabelPlaceholderHeight) / 2.0)), width: 120.0) + fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX + 120.0 + 10.0, y: currentY + floor((itemHeight - fakeLabelPlaceholderHeight) / 2.0)), width: 60.0) + + let dateFrame = itemNodes[sampleIndex].dateNode.frame.offsetBy(dx: 0.0, dy: currentY) + fillLabelPlaceholderRect(origin: CGPoint(x: dateFrame.maxX - 30.0, y: dateFrame.minY), width: 30.0) + + context.setBlendMode(.normal) + context.setFillColor(presentationData.theme.chatList.itemSeparatorColor.cgColor) + context.fill(itemNodes[sampleIndex].separatorNode.frame.offsetBy(dx: 0.0, dy: currentY)) + + currentY += itemHeight + } + }) + + self.effectNode.update(backgroundColor: presentationData.theme.list.mediaPlaceholderColor, foregroundColor: presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4)) + self.effectNode.updateAbsoluteRect(CGRect(origin: CGPoint(), size: size), within: size) + } + transition.updateFrame(node: self.backgroundColorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size)) + transition.updateFrame(node: self.maskNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size)) + transition.updateFrame(node: self.effectNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size)) + } +} + +private final class ChatListContainerItemNode: ASDisplayNode { + private let context: AccountContext + private var presentationData: PresentationData + private let becameEmpty: (ChatListFilter?) -> Void + private let emptyAction: (ChatListFilter?) -> Void + + private var floatingHeaderOffset: CGFloat? + + private var emptyNode: ChatListEmptyNode? + var emptyShimmerEffectNode: ChatListShimmerNode? + let listNode: ChatListNode + + private var validLayout: (CGSize, UIEdgeInsets, CGFloat)? + + init(context: AccountContext, groupId: PeerGroupId, filter: ChatListFilter?, previewing: Bool, presentationData: PresentationData, becameEmpty: @escaping (ChatListFilter?) -> Void, emptyAction: @escaping (ChatListFilter?) -> Void) { + self.context = context + self.presentationData = presentationData + self.becameEmpty = becameEmpty + self.emptyAction = emptyAction + + self.listNode = ChatListNode(context: context, groupId: groupId, chatListFilter: filter, previewing: previewing, controlsHistoryPreload: false, mode: .chatList, theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations) + + super.init() + + self.addSubnode(self.listNode) + + self.listNode.isEmptyUpdated = { [weak self] isEmptyState, _, transition in + guard let strongSelf = self else { + return + } + var needsShimmerNode = false + switch isEmptyState { + case let .empty(isLoading): + if isLoading { + needsShimmerNode = true + + if let emptyNode = strongSelf.emptyNode { + strongSelf.emptyNode = nil + transition.updateAlpha(node: emptyNode, alpha: 0.0, completion: { [weak emptyNode] _ in + emptyNode?.removeFromSupernode() + }) + } + } else { + if let currentNode = strongSelf.emptyNode { + currentNode.updateIsLoading(isLoading) + } else { + let emptyNode = ChatListEmptyNode(isFilter: filter != nil, isLoading: isLoading, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, action: { + self?.emptyAction(filter) + }) + strongSelf.emptyNode = emptyNode + strongSelf.addSubnode(emptyNode) + if let (size, insets, _) = strongSelf.validLayout { + let emptyNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom)) + emptyNode.frame = emptyNodeFrame + emptyNode.updateLayout(size: emptyNodeFrame.size, transition: .immediate) + } + emptyNode.alpha = 0.0 + transition.updateAlpha(node: emptyNode, alpha: 1.0) + } + } + if !isLoading { + strongSelf.becameEmpty(filter) + } + case .notEmpty: + if let emptyNode = strongSelf.emptyNode { + strongSelf.emptyNode = nil + transition.updateAlpha(node: emptyNode, alpha: 0.0, completion: { [weak emptyNode] _ in + emptyNode?.removeFromSupernode() + }) + } + } + if needsShimmerNode { + if strongSelf.emptyShimmerEffectNode == nil { + let emptyShimmerEffectNode = ChatListShimmerNode() + strongSelf.emptyShimmerEffectNode = emptyShimmerEffectNode + strongSelf.addSubnode(emptyShimmerEffectNode) + if let (size, insets, _) = strongSelf.validLayout, let offset = strongSelf.floatingHeaderOffset { + strongSelf.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: size, insets: insets, verticalOffset: offset, transition: .immediate) + } + } + } else if let emptyShimmerEffectNode = strongSelf.emptyShimmerEffectNode { + strongSelf.emptyShimmerEffectNode = nil + let emptyNodeTransition = transition.isAnimated ? transition : .animated(duration: 0.3, curve: .easeInOut) + emptyNodeTransition.updateAlpha(node: emptyShimmerEffectNode, alpha: 0.0, completion: { [weak emptyShimmerEffectNode] _ in + emptyShimmerEffectNode?.removeFromSupernode() + }) + } + } + + self.listNode.updateFloatingHeaderOffset = { [weak self] offset, transition in + guard let strongSelf = self else { + return + } + strongSelf.floatingHeaderOffset = offset + if let (size, insets, _) = strongSelf.validLayout, let emptyShimmerEffectNode = strongSelf.emptyShimmerEffectNode { + strongSelf.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: size, insets: insets, verticalOffset: offset, transition: transition) + } + } + } + + private func layoutEmptyShimmerEffectNode(node: ChatListShimmerNode, size: CGSize, insets: UIEdgeInsets, verticalOffset: CGFloat, transition: ContainedViewLayoutTransition) { + node.update(context: self.context, size: size, presentationData: self.presentationData, transition: .immediate) + transition.updateFrameAdditive(node: node, frame: CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: size)) + } + + func updatePresentationData(_ presentationData: PresentationData) { + self.presentationData = presentationData + + self.listNode.updateThemeAndStrings(theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations) + + self.emptyNode?.updateThemeAndStrings(theme: presentationData.theme, strings: presentationData.strings) + } + + func updateLayout(size: CGSize, insets: UIEdgeInsets, visualNavigationHeight: CGFloat, transition: ContainedViewLayoutTransition) { + self.validLayout = (size, insets, visualNavigationHeight) + + let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) + let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: size, insets: insets, duration: duration, curve: curve) + + transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(), size: size)) + self.listNode.visualInsets = UIEdgeInsets(top: visualNavigationHeight, left: 0.0, bottom: 0.0, right: 0.0) + self.listNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets) + + if let emptyNode = self.emptyNode { + let emptyNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom)) + transition.updateFrame(node: emptyNode, frame: emptyNodeFrame) + emptyNode.updateLayout(size: emptyNodeFrame.size, transition: transition) + } + } +} + +final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { + private let context: AccountContext + private let groupId: PeerGroupId + private let previewing: Bool + private let filterBecameEmpty: (ChatListFilter?) -> Void + private let filterEmptyAction: (ChatListFilter?) -> Void + + private var presentationData: PresentationData + + private var itemNodes: [ChatListFilterTabEntryId: ChatListContainerItemNode] = [:] + private var pendingItemNode: (ChatListFilterTabEntryId, ChatListContainerItemNode, Disposable)? + private var availableFilters: [ChatListContainerNodeFilter] = [.all] + private var selectedId: ChatListFilterTabEntryId + + private(set) var transitionFraction: CGFloat = 0.0 + private var transitionFractionOffset: CGFloat = 0.0 + private var disableItemNodeOperationsWhileAnimating: Bool = false + private var validLayout: (layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat, isReorderingFilters: Bool, isEditing: Bool)? + + private var enableAdjacentFilterLoading: Bool = false + + private var panRecognizer: InteractiveTransitionGestureRecognizer? + + private let _ready = Promise() + var ready: Signal { + return _ready.get() + } + + private var currentItemNodeValue: ChatListContainerItemNode? + var currentItemNode: ChatListNode { + return self.currentItemNodeValue!.listNode + } + + private let currentItemStateValue = Promise() + var currentItemState: Signal { + return self.currentItemStateValue.get() + } + + var currentItemFilterUpdated: ((ChatListFilterTabEntryId, CGFloat, ContainedViewLayoutTransition, Bool) -> Void)? + var currentItemFilter: ChatListFilterTabEntryId { + return self.currentItemNode.chatListFilter.flatMap { .filter($0.id) } ?? .all + } + + private func applyItemNodeAsCurrent(id: ChatListFilterTabEntryId, itemNode: ChatListContainerItemNode) { + if let previousItemNode = self.currentItemNodeValue { + previousItemNode.listNode.activateSearch = nil + previousItemNode.listNode.presentAlert = nil + previousItemNode.listNode.present = nil + previousItemNode.listNode.toggleArchivedFolderHiddenByDefault = nil + previousItemNode.listNode.deletePeerChat = nil + previousItemNode.listNode.peerSelected = nil + previousItemNode.listNode.groupSelected = nil + previousItemNode.listNode.updatePeerGrouping = nil + previousItemNode.listNode.contentOffsetChanged = nil + previousItemNode.listNode.contentScrollingEnded = nil + previousItemNode.listNode.activateChatPreview = nil + previousItemNode.listNode.addedVisibleChatsWithPeerIds = nil + + previousItemNode.accessibilityElementsHidden = true + } + self.currentItemNodeValue = itemNode + itemNode.accessibilityElementsHidden = false + + itemNode.listNode.activateSearch = { [weak self] in + self?.activateSearch?() + } + itemNode.listNode.presentAlert = { [weak self] text in + self?.presentAlert?(text) + } + itemNode.listNode.present = { [weak self] c in + self?.present?(c) + } + itemNode.listNode.toggleArchivedFolderHiddenByDefault = { [weak self] in + self?.toggleArchivedFolderHiddenByDefault?() + } + itemNode.listNode.deletePeerChat = { [weak self] peerId in + self?.deletePeerChat?(peerId) + } + itemNode.listNode.peerSelected = { [weak self] peerId, a, b in + self?.peerSelected?(peerId, a, b) + } + itemNode.listNode.groupSelected = { [weak self] groupId in + self?.groupSelected?(groupId) + } + itemNode.listNode.updatePeerGrouping = { [weak self] peerId, group in + self?.updatePeerGrouping?(peerId, group) + } + itemNode.listNode.contentOffsetChanged = { [weak self] offset in + self?.contentOffsetChanged?(offset) + } + itemNode.listNode.contentScrollingEnded = { [weak self] listView in + return self?.contentScrollingEnded?(listView) ?? false + } + itemNode.listNode.activateChatPreview = { [weak self] item, sourceNode, gesture in + self?.activateChatPreview?(item, sourceNode, gesture) + } + itemNode.listNode.addedVisibleChatsWithPeerIds = { [weak self] ids in + self?.addedVisibleChatsWithPeerIds?(ids) + } + + self.currentItemStateValue.set(itemNode.listNode.state) + } + + var activateSearch: (() -> Void)? + var presentAlert: ((String) -> Void)? + var present: ((ViewController) -> Void)? + var toggleArchivedFolderHiddenByDefault: (() -> Void)? + var deletePeerChat: ((PeerId) -> Void)? + var peerSelected: ((Peer, Bool, Bool) -> Void)? + var groupSelected: ((PeerGroupId) -> Void)? + var updatePeerGrouping: ((PeerId, Bool) -> Void)? + var contentOffsetChanged: ((ListViewVisibleContentOffset) -> Void)? + var contentScrollingEnded: ((ListView) -> Bool)? + var activateChatPreview: ((ChatListItem, ASDisplayNode, ContextGesture?) -> Void)? + var addedVisibleChatsWithPeerIds: (([PeerId]) -> Void)? + + init(context: AccountContext, groupId: PeerGroupId, previewing: Bool, presentationData: PresentationData, filterBecameEmpty: @escaping (ChatListFilter?) -> Void, filterEmptyAction: @escaping (ChatListFilter?) -> Void) { + self.context = context + self.groupId = groupId + self.previewing = previewing + self.filterBecameEmpty = filterBecameEmpty + self.filterEmptyAction = filterEmptyAction + + self.presentationData = presentationData + + self.selectedId = .all + + super.init() + + let itemNode = ChatListContainerItemNode(context: self.context, groupId: self.groupId, filter: nil, previewing: self.previewing, presentationData: presentationData, becameEmpty: { [weak self] filter in + self?.filterBecameEmpty(filter) + }, emptyAction: { [weak self] filter in + self?.filterEmptyAction(filter) + }) + self.itemNodes[.all] = itemNode + self.addSubnode(itemNode) + + self._ready.set(itemNode.listNode.ready) + + self.applyItemNodeAsCurrent(id: .all, itemNode: itemNode) + + let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] _ in + guard let strongSelf = self, strongSelf.availableFilters.count > 1 else { + return [] + } + let directions: InteractiveTransitionGestureRecognizerDirections = [.leftCenter, .rightCenter] + return directions + }, edgeWidth: .widthMultiplier(factor: 1.0 / 6.0, min: 22.0, max: 80.0)) + panRecognizer.delegate = self + panRecognizer.delaysTouchesBegan = false + panRecognizer.cancelsTouchesInView = true + self.panRecognizer = panRecognizer + self.view.addGestureRecognizer(panRecognizer) + } + + deinit { + self.pendingItemNode?.2.dispose() + } + + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return false + } + + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool { + if let _ = otherGestureRecognizer as? InteractiveTransitionGestureRecognizer { + return false + } + if let _ = otherGestureRecognizer as? UIPanGestureRecognizer { + return true + } + return false + } + + @objc private func panGesture(_ recognizer: UIPanGestureRecognizer) { + switch recognizer.state { + case .began: + self.transitionFractionOffset = 0.0 + if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, isReorderingFilters, isEditing) = self.validLayout, let itemNode = self.itemNodes[self.selectedId] { + if let presentationLayer = itemNode.layer.presentation() { + self.transitionFraction = presentationLayer.frame.minX / layout.size.width + self.transitionFractionOffset = self.transitionFraction + if !self.transitionFraction.isZero { + for (_, itemNode) in self.itemNodes { + itemNode.layer.removeAllAnimations() + } + self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: .immediate) + self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, .immediate, true) + } + } + } + case .changed: + if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, isReorderingFilters, isEditing) = self.validLayout, let selectedIndex = self.availableFilters.firstIndex(where: { $0.id == self.selectedId }) { + let translation = recognizer.translation(in: self.view) + var transitionFraction = translation.x / layout.size.width + + func rubberBandingOffset(offset: CGFloat, bandingStart: CGFloat) -> CGFloat { + let bandedOffset = offset - bandingStart + let range: CGFloat = 600.0 + let coefficient: CGFloat = 0.4 + return bandingStart + (1.0 - (1.0 / ((bandedOffset * coefficient / range) + 1.0))) * range + } + + if selectedIndex <= 0 && translation.x > 0.0 { + let overscroll = translation.x + transitionFraction = rubberBandingOffset(offset: overscroll, bandingStart: 0.0) / layout.size.width + } + if selectedIndex >= self.availableFilters.count - 1 && translation.x < 0.0 { + let overscroll = -translation.x + transitionFraction = -rubberBandingOffset(offset: overscroll, bandingStart: 0.0) / layout.size.width + } + self.transitionFraction = transitionFraction + self.transitionFractionOffset + if let currentItemNode = self.currentItemNodeValue { + let isNavigationHidden = currentItemNode.listNode.isNavigationHidden + for (_, itemNode) in self.itemNodes { + if itemNode !== currentItemNode { + itemNode.listNode.adjustScrollOffsetForNavigation(isNavigationHidden: isNavigationHidden) + } + } + } + self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: .immediate) + self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, .immediate, false) + } + case .cancelled, .ended: + if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, isReorderingFilters, isEditing) = self.validLayout, let selectedIndex = self.availableFilters.firstIndex(where: { $0.id == self.selectedId }) { + let translation = recognizer.translation(in: self.view) + let velocity = recognizer.velocity(in: self.view) + var directionIsToRight: Bool? + if abs(velocity.x) > 10.0 { + if translation.x < 0.0 { + if velocity.x >= 0.0 { + directionIsToRight = nil + } else { + directionIsToRight = true + } + } else { + if velocity.x <= 0.0 { + directionIsToRight = nil + } else { + directionIsToRight = false + } + } + } else { + if abs(translation.x) > layout.size.width / 2.0 { + directionIsToRight = translation.x > layout.size.width / 2.0 + } + } + if let directionIsToRight = directionIsToRight { + var updatedIndex = selectedIndex + if directionIsToRight { + updatedIndex = min(updatedIndex + 1, self.availableFilters.count - 1) + } else { + updatedIndex = max(updatedIndex - 1, 0) + } + let switchToId = self.availableFilters[updatedIndex].id + if switchToId != self.selectedId, let itemNode = self.itemNodes[switchToId] { + self.selectedId = switchToId + self.applyItemNodeAsCurrent(id: switchToId, itemNode: itemNode) + } + } + self.transitionFraction = 0.0 + let transition: ContainedViewLayoutTransition = .animated(duration: 0.45, curve: .spring) + self.disableItemNodeOperationsWhileAnimating = true + self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: transition) + self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition, false) + DispatchQueue.main.async { + self.disableItemNodeOperationsWhileAnimating = false + if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, isReorderingFilters, isEditing) = self.validLayout { + self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: .immediate) + } + } + } + default: + break + } + } + + func updatePresentationData(_ presentationData: PresentationData) { + self.presentationData = presentationData + + for (_, itemNode) in self.itemNodes { + itemNode.updatePresentationData(presentationData) + } + } + + func playArchiveAnimation() { + if let itemNode = self.itemNodes[self.selectedId] { + itemNode.listNode.forEachVisibleItemNode { node in + if let node = node as? ChatListItemNode { + node.playArchiveAnimation() + } + } + } + } + + func scrollToTop() { + if let itemNode = self.itemNodes[self.selectedId] { + itemNode.listNode.scrollToPosition(.top) + } + } + + func updateSelectedChatLocation(data: ChatLocation?, progress: CGFloat, transition: ContainedViewLayoutTransition) { + for (_, itemNode) in self.itemNodes { + itemNode.listNode.updateSelectedChatLocation(data, progress: progress, transition: transition) + } + } + + func updateState(_ f: (ChatListNodeState) -> ChatListNodeState) { + self.currentItemNode.updateState(f) + let updatedState = self.currentItemNode.currentState + for (id, itemNode) in self.itemNodes { + if id != self.selectedId { + itemNode.listNode.updateState { state in + var state = state + state.editing = updatedState.editing + state.selectedPeerIds = updatedState.selectedPeerIds + return state + } + } + } + } + + func updateAvailableFilters(_ availableFilters: [ChatListContainerNodeFilter]) { + if self.availableFilters != availableFilters { + let apply: () -> Void = { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.availableFilters = availableFilters + if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, isReorderingFilters, isEditing) = strongSelf.validLayout { + strongSelf.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: .immediate) + } + } + if !availableFilters.contains(where: { $0.id == self.selectedId }) { + self.switchToFilter(id: .all, completion: { + apply() + }) + } else { + apply() + } + } + } + + func updateEnableAdjacentFilterLoading(_ value: Bool) { + if value != self.enableAdjacentFilterLoading { + self.enableAdjacentFilterLoading = value + + if self.enableAdjacentFilterLoading, let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, isReorderingFilters, isEditing) = self.validLayout { + self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: .immediate) + } + } + } + + func switchToFilter(id: ChatListFilterTabEntryId, completion: (() -> Void)? = nil) { + guard let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, isReorderingFilters, isEditing) = self.validLayout else { + return + } + if id != self.selectedId, let index = self.availableFilters.firstIndex(where: { $0.id == id }) { + if let itemNode = self.itemNodes[id] { + self.selectedId = id + if let currentItemNode = self.currentItemNodeValue { + itemNode.listNode.adjustScrollOffsetForNavigation(isNavigationHidden: currentItemNode.listNode.isNavigationHidden) + } + self.applyItemNodeAsCurrent(id: id, itemNode: itemNode) + let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .spring) + self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: transition) + self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition, false) + completion?() + } else if self.pendingItemNode == nil { + let itemNode = ChatListContainerItemNode(context: self.context, groupId: self.groupId, filter: self.availableFilters[index].filter, previewing: self.previewing, presentationData: self.presentationData, becameEmpty: { [weak self] filter in + self?.filterBecameEmpty(filter) + }, emptyAction: { [weak self] filter in + self?.filterEmptyAction(filter) + }) + let disposable = MetaDisposable() + self.pendingItemNode = (id, itemNode, disposable) + + disposable.set((itemNode.listNode.ready + |> filter { $0 } + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self, weak itemNode] _ in + guard let strongSelf = self, let itemNode = itemNode, itemNode === strongSelf.pendingItemNode?.1 else { + return + } + guard let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, isReorderingFilters, isEditing) = strongSelf.validLayout else { + return + } + strongSelf.pendingItemNode = nil + + let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .spring) + + if let previousIndex = strongSelf.availableFilters.firstIndex(where: { $0.id == strongSelf.selectedId }), let index = strongSelf.availableFilters.firstIndex(where: { $0.id == id }) { + let previousId = strongSelf.selectedId + let offsetDirection: CGFloat = index < previousIndex ? 1.0 : -1.0 + let offset = offsetDirection * layout.size.width + + var validNodeIds: [ChatListFilterTabEntryId] = [] + for i in max(0, index - 1) ... min(strongSelf.availableFilters.count - 1, index + 1) { + validNodeIds.append(strongSelf.availableFilters[i].id) + } + + var removeIds: [ChatListFilterTabEntryId] = [] + for (id, _) in strongSelf.itemNodes { + if !validNodeIds.contains(id) { + removeIds.append(id) + } + } + for id in removeIds { + if let itemNode = strongSelf.itemNodes.removeValue(forKey: id) { + if id == previousId { + transition.updateFrame(node: itemNode, frame: itemNode.frame.offsetBy(dx: offset, dy: 0.0), completion: { [weak itemNode] _ in + itemNode?.removeFromSupernode() + }) + } else { + itemNode.removeFromSupernode() + } + } + } + + strongSelf.itemNodes[id] = itemNode + strongSelf.addSubnode(itemNode) + + let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: layout.size) + itemNode.frame = itemFrame + + transition.animatePositionAdditive(node: itemNode, offset: CGPoint(x: -offset, y: 0.0)) + + var insets = layout.insets(options: [.input]) + insets.top += navigationBarHeight + + insets.left += layout.safeInsets.left + insets.right += layout.safeInsets.right + + itemNode.updateLayout(size: layout.size, insets: insets, visualNavigationHeight: visualNavigationHeight, transition: .immediate) + + strongSelf.selectedId = id + if let currentItemNode = strongSelf.currentItemNodeValue { + itemNode.listNode.adjustScrollOffsetForNavigation(isNavigationHidden: currentItemNode.listNode.isNavigationHidden) + } + strongSelf.applyItemNodeAsCurrent(id: id, itemNode: itemNode) + + strongSelf.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: .immediate) + + strongSelf.currentItemFilterUpdated?(strongSelf.currentItemFilter, strongSelf.transitionFraction, transition, false) + } + + completion?() + })) + } + } + } + + func update(layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat, isReorderingFilters: Bool, isEditing: Bool, transition: ContainedViewLayoutTransition) { + self.validLayout = (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, isReorderingFilters, isEditing) + + var insets = layout.insets(options: [.input]) + insets.top += navigationBarHeight + + insets.left += layout.safeInsets.left + insets.right += layout.safeInsets.right + + transition.updateAlpha(node: self, alpha: isReorderingFilters ? 0.5 : 1.0) + self.isUserInteractionEnabled = !isReorderingFilters + + self.panRecognizer?.isEnabled = !isEditing + + if let selectedIndex = self.availableFilters.firstIndex(where: { $0.id == self.selectedId }) { + var validNodeIds: [ChatListFilterTabEntryId] = [] + for i in max(0, selectedIndex - 1) ... min(self.availableFilters.count - 1, selectedIndex + 1) { + let id = self.availableFilters[i].id + validNodeIds.append(id) + + if self.itemNodes[id] == nil && self.enableAdjacentFilterLoading && !self.disableItemNodeOperationsWhileAnimating { + let itemNode = ChatListContainerItemNode(context: self.context, groupId: self.groupId, filter: self.availableFilters[i].filter, previewing: self.previewing, presentationData: self.presentationData, becameEmpty: { [weak self] filter in + self?.filterBecameEmpty(filter) + }, emptyAction: { [weak self] filter in + self?.filterEmptyAction(filter) + }) + self.itemNodes[id] = itemNode + } + } + + var removeIds: [ChatListFilterTabEntryId] = [] + var animateSlidingIds: [ChatListFilterTabEntryId] = [] + var slidingOffset: CGFloat? + for (id, itemNode) in self.itemNodes { + if !validNodeIds.contains(id) { + removeIds.append(id) + } + guard let index = self.availableFilters.firstIndex(where: { $0.id == id }) else { + continue + } + let indexDistance = CGFloat(index - selectedIndex) + self.transitionFraction + + let wasAdded = itemNode.supernode == nil + var nodeTransition = transition + if wasAdded { + self.addSubnode(itemNode) + nodeTransition = .immediate + } + + let itemFrame = CGRect(origin: CGPoint(x: indexDistance * layout.size.width, y: 0.0), size: layout.size) + if !wasAdded && slidingOffset == nil { + slidingOffset = itemNode.frame.minX - itemFrame.minX + } + nodeTransition.updateFrame(node: itemNode, frame: itemFrame, completion: { _ in + }) + + itemNode.updateLayout(size: layout.size, insets: insets, visualNavigationHeight: visualNavigationHeight, transition: nodeTransition) + + if wasAdded, case .animated = transition { + animateSlidingIds.append(id) + } + } + if let slidingOffset = slidingOffset { + for id in animateSlidingIds { + if let itemNode = self.itemNodes[id] { + transition.animatePositionAdditive(node: itemNode, offset: CGPoint(x: slidingOffset, y: 0.0), completion: { + }) + } + } + } + if !self.disableItemNodeOperationsWhileAnimating { + for id in removeIds { + if let itemNode = self.itemNodes.removeValue(forKey: id) { + itemNode.removeFromSupernode() + } + } + } } - return lhs.value < rhs.value } } @@ -47,9 +934,7 @@ final class ChatListControllerNode: ASDisplayNode { private let groupId: PeerGroupId private var presentationData: PresentationData - private var chatListEmptyNodeContainer: ChatListEmptyNodeContainer - private var chatListEmptyIndicator: ActivityIndicator? - let chatListNode: ChatListNode + let containerNode: ChatListContainerNode var navigationBar: NavigationBar? weak var controller: ChatListControllerImpl? @@ -59,6 +944,9 @@ final class ChatListControllerNode: ASDisplayNode { private(set) var searchDisplayController: SearchDisplayController? + var isReorderingFilters: Bool = false + var isEditing: Bool = false + private var containerLayout: (ContainerViewLayout, CGFloat, CGFloat, CGFloat)? var requestDeactivateSearch: (() -> Void)? @@ -67,7 +955,7 @@ final class ChatListControllerNode: ASDisplayNode { var requestOpenMessageFromSearch: ((Peer, MessageId) -> Void)? var requestAddContact: ((String) -> Void)? var peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)? - var dismissSelf: (() -> Void)? + var dismissSelfIfCompletedPresentation: (() -> Void)? var isEmptyUpdated: ((Bool) -> Void)? var emptyListAction: (() -> Void)? @@ -78,8 +966,13 @@ final class ChatListControllerNode: ASDisplayNode { self.groupId = groupId self.presentationData = presentationData - self.chatListEmptyNodeContainer = ChatListEmptyNodeContainer(theme: presentationData.theme, strings: presentationData.strings) - self.chatListNode = ChatListNode(context: context, groupId: groupId, chatListFilter: filter, previewing: previewing, controlsHistoryPreload: controlsHistoryPreload, mode: .chatList, theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations) + var filterBecameEmpty: ((ChatListFilter?) -> Void)? + var filterEmptyAction: ((ChatListFilter?) -> Void)? + self.containerNode = ChatListContainerNode(context: context, groupId: groupId, previewing: previewing, presentationData: presentationData, filterBecameEmpty: { filter in + filterBecameEmpty?(filter) + }, filterEmptyAction: { filter in + filterEmptyAction?(filter) + }) self.controller = controller @@ -91,35 +984,24 @@ final class ChatListControllerNode: ASDisplayNode { self.backgroundColor = presentationData.theme.chatList.backgroundColor - self.addSubnode(self.chatListNode) - self.addSubnode(self.chatListEmptyNodeContainer) - self.chatListNode.isEmptyUpdated = { [weak self] isEmptyState, isFilter, transitionDirection, transition in + self.addSubnode(self.containerNode) + + self.addSubnode(self.debugListView) + + filterBecameEmpty = { [weak self] _ in guard let strongSelf = self else { return } - switch isEmptyState { - case .empty(false): - if case .group = strongSelf.groupId { - strongSelf.dismissSelf?() - } else { - strongSelf.chatListEmptyNodeContainer.update(state: isEmptyState, isFilter: isFilter, direction: transitionDirection, transition: transition) - } - case .notEmpty(false): - if case .group = strongSelf.groupId { - strongSelf.dismissSelf?() - } else { - strongSelf.chatListEmptyNodeContainer.update(state: isEmptyState, isFilter: isFilter, direction: transitionDirection, transition: transition) - } - default: - strongSelf.chatListEmptyNodeContainer.update(state: isEmptyState, isFilter: isFilter, direction: transitionDirection, transition: transition) + if case .group = strongSelf.groupId { + strongSelf.dismissSelfIfCompletedPresentation?() } } - - self.chatListEmptyNodeContainer.action = { [weak self] in - self?.emptyListAction?() + filterEmptyAction = { [weak self] filter in + guard let strongSelf = self else { + return + } + strongSelf.emptyListAction?() } - - self.addSubnode(self.debugListView) } override func didLoad() { @@ -133,9 +1015,8 @@ final class ChatListControllerNode: ASDisplayNode { self.backgroundColor = self.presentationData.theme.chatList.backgroundColor - self.chatListNode.updateThemeAndStrings(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: self.presentationData.disableAnimations) + self.containerNode.updatePresentationData(presentationData) self.searchDisplayController?.updatePresentationData(presentationData) - self.chatListEmptyNodeContainer.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings) if let toolbarNode = self.toolbarNode { toolbarNode.updateTheme(TabBarControllerTheme(rootControllerTheme: self.presentationData.theme)) @@ -194,23 +1075,8 @@ final class ChatListControllerNode: ASDisplayNode { }) } - self.chatListNode.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height) - self.chatListNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0) - - let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) - let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, duration: duration, curve: curve) - - self.chatListNode.visualInsets = UIEdgeInsets(top: visualNavigationHeight, left: 0.0, bottom: 0.0, right: 0.0) - self.chatListNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets) - - let emptySize = CGSize(width: updateSizeAndInsets.size.width, height: updateSizeAndInsets.size.height - updateSizeAndInsets.insets.top - updateSizeAndInsets.insets.bottom) - transition.updateFrame(node: self.chatListEmptyNodeContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: updateSizeAndInsets.insets.top), size: emptySize)) - self.chatListEmptyNodeContainer.updateLayout(size: emptySize, transition: transition) - - if let chatListEmptyIndicator = self.chatListEmptyIndicator { - let indicatorSize = chatListEmptyIndicator.measure(CGSize(width: 100.0, height: 100.0)) - transition.updateFrame(node: chatListEmptyIndicator, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - indicatorSize.width) / 2.0), y: updateSizeAndInsets.insets.top + floor((layout.size.height - updateSizeAndInsets.insets.top - updateSizeAndInsets.insets.bottom - indicatorSize.height) / 2.0)), size: indicatorSize)) - } + transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: layout.size)) + self.containerNode.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, isReorderingFilters: self.isReorderingFilters, isEditing: self.isEditing, transition: transition) if let searchDisplayController = self.searchDisplayController { searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: cleanNavigationBarHeight, transition: transition) @@ -218,7 +1084,7 @@ final class ChatListControllerNode: ASDisplayNode { } func activateSearch(placeholderNode: SearchBarPlaceholderNode) { - guard let (containerLayout, navigationBarHeight, _, cleanNavigationBarHeight) = self.containerLayout, let navigationBar = self.navigationBar, self.searchDisplayController == nil else { + guard let (containerLayout, _, _, cleanNavigationBarHeight) = self.containerLayout, let navigationBar = self.navigationBar, self.searchDisplayController == nil else { return } @@ -242,7 +1108,7 @@ final class ChatListControllerNode: ASDisplayNode { requestDeactivateSearch() } }) - self.chatListNode.accessibilityElementsHidden = true + self.containerNode.accessibilityElementsHidden = true self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: cleanNavigationBarHeight, transition: .immediate) self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in @@ -260,23 +1126,19 @@ final class ChatListControllerNode: ASDisplayNode { if let searchDisplayController = self.searchDisplayController { searchDisplayController.deactivate(placeholder: placeholderNode, animated: animated) self.searchDisplayController = nil - self.chatListNode.accessibilityElementsHidden = false + self.containerNode.accessibilityElementsHidden = false } } func playArchiveAnimation() { - self.chatListNode.forEachVisibleItemNode { node in - if let node = node as? ChatListItemNode { - node.playArchiveAnimation() - } - } + self.containerNode.playArchiveAnimation() } func scrollToTop() { if let searchDisplayController = self.searchDisplayController { searchDisplayController.contentNode.scrollToTop() } else { - self.chatListNode.scrollToPosition(.top) + self.containerNode.scrollToTop() } } } diff --git a/submodules/ChatListUI/Sources/ChatListEmptyNode.swift b/submodules/ChatListUI/Sources/ChatListEmptyNode.swift index 4176e5ed4d..63f0352da2 100644 --- a/submodules/ChatListUI/Sources/ChatListEmptyNode.swift +++ b/submodules/ChatListUI/Sources/ChatListEmptyNode.swift @@ -8,138 +8,6 @@ import AppBundle import SolidRoundedButtonNode import ActivityIndicator -final class ChatListEmptyNodeContainer: ASDisplayNode { - private var currentNode: ChatListEmptyNode? - - private var theme: PresentationTheme - private var strings: PresentationStrings - private var validLayout: CGSize? - - var action: (() -> Void)? - - init(theme: PresentationTheme, strings: PresentationStrings) { - self.theme = theme - self.strings = strings - - super.init() - } - - func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { - self.theme = theme - self.strings = strings - - if let currentNode = self.currentNode { - currentNode.updateThemeAndStrings(theme: theme, strings: strings) - } - } - - func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { - self.validLayout = size - - if let currentNode = self.currentNode { - currentNode.updateLayout(size: size, transition: transition) - } - } - - func update(state: ChatListNodeEmptyState, isFilter: Bool, direction: ChatListNodePaneSwitchAnimationDirection?, transition: ContainedViewLayoutTransition) { - switch state { - case let .empty(isLoading): - if let direction = direction { - let previousNode = self.currentNode - let currentNode = ChatListEmptyNode(isFilter: isFilter, isLoading: isLoading, theme: self.theme, strings: self.strings, action: { [weak self] in - self?.action?() - }) - self.currentNode = currentNode - if let size = self.validLayout { - currentNode.frame = CGRect(origin: CGPoint(), size: size) - currentNode.updateLayout(size: size, transition: .immediate) - } - self.addSubnode(currentNode) - if case .animated = transition, let size = self.validLayout { - let offset: CGFloat - switch direction { - case .left: - offset = -size.width - case .right: - offset = size.width - } - if let previousNode = previousNode { - previousNode.frame = self.bounds.offsetBy(dx: offset, dy: 0.0) - } - transition.animateHorizontalOffsetAdditive(node: self, offset: offset, completion: { [weak previousNode] in - previousNode?.removeFromSupernode() - }) - } else { - previousNode?.removeFromSupernode() - } - } else { - if let previousNode = self.currentNode, previousNode.isFilter != isFilter { - let currentNode = ChatListEmptyNode(isFilter: isFilter, isLoading: isLoading, theme: self.theme, strings: self.strings, action: { [weak self] in - self?.action?() - }) - self.currentNode = currentNode - if let size = self.validLayout { - currentNode.frame = CGRect(origin: CGPoint(), size: size) - currentNode.updateLayout(size: size, transition: .immediate) - } - self.addSubnode(currentNode) - currentNode.alpha = 0.0 - transition.updateAlpha(node: currentNode, alpha: 1.0) - transition.updateAlpha(node: previousNode, alpha: 0.0, completion: { [weak previousNode] _ in - previousNode?.removeFromSupernode() - }) - } else if let currentNode = self.currentNode { - currentNode.updateIsLoading(isLoading) - } else { - let currentNode = ChatListEmptyNode(isFilter: isFilter, isLoading: isLoading, theme: self.theme, strings: self.strings, action: { [weak self] in - self?.action?() - }) - self.currentNode = currentNode - if let size = self.validLayout { - currentNode.frame = CGRect(origin: CGPoint(), size: size) - currentNode.updateLayout(size: size, transition: .immediate) - } - self.addSubnode(currentNode) - currentNode.alpha = 0.0 - transition.updateAlpha(node: currentNode, alpha: 1.0) - } - } - case .notEmpty: - if let previousNode = self.currentNode { - self.currentNode = nil - if let direction = direction { - if case .animated = transition, let size = self.validLayout { - let offset: CGFloat - switch direction { - case .left: - offset = -size.width - case .right: - offset = size.width - } - previousNode.frame = self.bounds.offsetBy(dx: offset, dy: 0.0) - transition.animateHorizontalOffsetAdditive(node: self, offset: offset, completion: { [weak previousNode] in - previousNode?.removeFromSupernode() - }) - } else { - previousNode.removeFromSupernode() - } - } else { - transition.updateAlpha(node: previousNode, alpha: 0.0, completion: { [weak previousNode] _ in - previousNode?.removeFromSupernode() - }) - } - } - } - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if let currentNode = self.currentNode { - return currentNode.view.hitTest(self.view.convert(point, to: currentNode.view), with: event) - } - return nil - } -} - final class ChatListEmptyNode: ASDisplayNode { let isFilter: Bool private(set) var isLoading: Bool @@ -174,6 +42,7 @@ final class ChatListEmptyNode: ASDisplayNode { self.addSubnode(self.animationNode) self.addSubnode(self.textNode) self.addSubnode(self.buttonNode) + self.addSubnode(self.activityIndicator) let animationName: String if isFilter { diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetCategoryItem.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetCategoryItem.swift new file mode 100644 index 0000000000..52751ffef9 --- /dev/null +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetCategoryItem.swift @@ -0,0 +1,471 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import SwiftSignalKit +import ItemListUI +import CheckNode +import AvatarNode +import AccountContext +import TelegramPresentationData +import ChatListSearchItemHeader + +enum ChatListFilterCategoryIcon { + case contacts + case nonContacts + case groups + case channels + case bots + case muted + case read + case archived +} + +final class ChatListFilterPresetCategoryItem: ListViewItem, ItemListItem { + let presentationData: ItemListPresentationData + let title: String + let icon: ChatListFilterCategoryIcon + let isRevealed: Bool + let selectable: Bool = false + let sectionId: ItemListSectionId + let updatedRevealedOptions: (Bool) -> Void + let remove: () -> Void + + init( + presentationData: ItemListPresentationData, + title: String, + icon: ChatListFilterCategoryIcon, + isRevealed: Bool, + sectionId: ItemListSectionId, + updatedRevealedOptions: @escaping (Bool) -> Void, + remove: @escaping () -> Void + ) { + self.presentationData = presentationData + self.title = title + self.icon = icon + self.isRevealed = isRevealed + self.sectionId = sectionId + self.updatedRevealedOptions = updatedRevealedOptions + self.remove = remove + } + + func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { + async { + let node = ChatListFilterPresetCategoryItemNode() + let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem), false) + + node.contentSize = layout.contentSize + node.insets = layout.insets + + Queue.mainQueue().async { + completion(node, { + return (.complete(), { _ in apply(synchronousLoads, false) }) + }) + } + } + } + + func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { + Queue.mainQueue().async { + if let nodeValue = node() as? ChatListFilterPresetCategoryItemNode { + let makeLayout = nodeValue.asyncLayout() + + var animated = true + if case .None = animation { + animated = false + } + + async { + let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem), false) + Queue.mainQueue().async { + completion(layout, { _ in + apply(false, animated) + }) + } + } + } + } + } + + func selected(listView: ListView){ + listView.clearHighlightAnimated(true) + } +} + +private let avatarFont = avatarPlaceholderFont(size: floor(40.0 * 16.0 / 37.0)) +private let badgeFont = Font.regular(15.0) + +class ChatListFilterPresetCategoryItemNode: ItemListRevealOptionsItemNode, ItemListItemNode { + private let backgroundNode: ASDisplayNode + private let topStripeNode: ASDisplayNode + private let bottomStripeNode: ASDisplayNode + private let highlightedBackgroundNode: ASDisplayNode + private let maskNode: ASImageNode + + private let avatarNode: ASImageNode + private let titleNode: TextNode + + private var item: ChatListFilterPresetCategoryItem? + private var layoutParams: ListViewItemLayoutParams? + + private var editableControlNode: ItemListEditableControlNode? + + override var canBeSelected: Bool { + if self.editableControlNode != nil { + return false + } + return false + } + + var tag: ItemListItemTag? { + return nil + } + + init() { + self.backgroundNode = ASDisplayNode() + self.backgroundNode.isLayerBacked = true + + self.topStripeNode = ASDisplayNode() + self.topStripeNode.isLayerBacked = true + + self.bottomStripeNode = ASDisplayNode() + self.bottomStripeNode.isLayerBacked = true + self.maskNode = ASImageNode() + + self.avatarNode = ASImageNode() + self.avatarNode.isUserInteractionEnabled = false + + self.titleNode = TextNode() + self.titleNode.isUserInteractionEnabled = false + self.titleNode.contentMode = .left + self.titleNode.contentsScale = UIScreen.main.scale + + self.highlightedBackgroundNode = ASDisplayNode() + self.highlightedBackgroundNode.isLayerBacked = true + + super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + + self.isAccessibilityElement = true + + self.addSubnode(self.avatarNode) + self.addSubnode(self.titleNode) + } + + override func didLoad() { + super.didLoad() + } + + func asyncLayout() -> (_ item: ChatListFilterPresetCategoryItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors, _ headerAtTop: Bool) -> (ListViewItemNodeLayout, (Bool, Bool) -> Void) { + let makeTitleLayout = TextNode.asyncLayout(self.titleNode) + let editableControlLayout = ItemListEditableControlNode.asyncLayout(self.editableControlNode) + + let currentItem = self.item + + return { item, params, neighbors, headerAtTop in + var updatedTheme: PresentationTheme? + + let titleFont = Font.medium(item.presentationData.fontSize.itemListBaseFontSize) + + if currentItem?.presentationData.theme !== item.presentationData.theme { + updatedTheme = item.presentationData.theme + } + + var titleAttributedString: NSAttributedString? + + let peerRevealOptions: [ItemListRevealOption] + peerRevealOptions = [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)] + + let rightInset: CGFloat = params.rightInset + + let titleColor: UIColor + titleColor = item.presentationData.theme.list.itemPrimaryTextColor + + + titleAttributedString = NSAttributedString(string: item.title, font: titleFont, textColor: titleColor) + + let leftInset: CGFloat + let verticalInset: CGFloat + let verticalOffset: CGFloat + let avatarSize: CGFloat + + verticalInset = 14.0 + verticalOffset = 0.0 + avatarSize = 40.0 + leftInset = 65.0 + params.leftInset + + var editableControlSizeAndApply: (CGFloat, (CGFloat) -> ItemListEditableControlNode)? + + let editingOffset: CGFloat + if false { + let sizeAndApply = editableControlLayout(item.presentationData.theme, false) + editableControlSizeAndApply = sizeAndApply + editingOffset = sizeAndApply.0 + } else { + editingOffset = 0.0 + } + + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 12.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let insets = itemListNeighborsGroupedInsets(neighbors) + + let minHeight: CGFloat = titleLayout.size.height + verticalInset * 2.0 + let rawHeight: CGFloat = verticalInset * 2.0 + titleLayout.size.height + + let contentSize = CGSize(width: params.width, height: max(minHeight, rawHeight)) + let separatorHeight = UIScreenPixel + + let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) + let layoutSize = layout.size + + let hadAvatarImage = self.avatarNode.image != nil + + return (layout, { [weak self] synchronousLoad, animated in + if let strongSelf = self { + strongSelf.item = item + strongSelf.layoutParams = params + + strongSelf.accessibilityLabel = titleAttributedString?.string + + if let _ = updatedTheme { + strongSelf.topStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor + strongSelf.bottomStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor + strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor + strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor + } + + var updatedAvatarImage: UIImage? + if !hadAvatarImage { + let color: AvatarBackgroundColor + let imageName: String + switch item.icon { + case .contacts: + color = .blue + imageName = "Chat/Context Menu/User" + case .nonContacts: + color = .yellow + imageName = "Chat/Context Menu/UnknownUser" + case .groups: + color = .green + imageName = "Chat/Context Menu/Groups" + case .channels: + color = .red + imageName = "Chat/Context Menu/Channels" + case .bots: + color = .violet + imageName = "Chat/Context Menu/Bots" + case .muted: + color = .red + imageName = "Chat/Context Menu/Muted" + case .read: + color = .blue + imageName = "Chat/Context Menu/Message" + case .archived: + color = .yellow + imageName = "Chat/Context Menu/Archive" + } + updatedAvatarImage = generateAvatarImage(size: CGSize(width: avatarSize, height: avatarSize), icon: generateTintedImage(image: UIImage(bundleImageName: imageName), color: .white), color: color) + } + + let revealOffset = strongSelf.revealOffset + + let transition: ContainedViewLayoutTransition + if animated { + transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) + } else { + transition = .immediate + } + + + if let editableControlSizeAndApply = editableControlSizeAndApply { + let editableControlFrame = CGRect(origin: CGPoint(x: params.leftInset + revealOffset, y: 0.0), size: CGSize(width: editableControlSizeAndApply.0, height: layout.contentSize.height)) + if strongSelf.editableControlNode == nil { + let editableControlNode = editableControlSizeAndApply.1(layout.contentSize.height) + editableControlNode.tapped = { + if let strongSelf = self { + strongSelf.setRevealOptionsOpened(true, animated: true) + strongSelf.revealOptionsInteractivelyOpened() + } + } + strongSelf.editableControlNode = editableControlNode + strongSelf.addSubnode(editableControlNode) + editableControlNode.frame = editableControlFrame + transition.animatePosition(node: editableControlNode, from: CGPoint(x: -editableControlFrame.size.width / 2.0, y: editableControlFrame.midY)) + editableControlNode.alpha = 0.0 + transition.updateAlpha(node: editableControlNode, alpha: 1.0) + } else { + strongSelf.editableControlNode?.frame = editableControlFrame + } + strongSelf.editableControlNode?.isHidden = true + } else if let editableControlNode = strongSelf.editableControlNode { + var editableControlFrame = editableControlNode.frame + editableControlFrame.origin.x = -editableControlFrame.size.width + strongSelf.editableControlNode = nil + transition.updateAlpha(node: editableControlNode, alpha: 0.0) + transition.updateFrame(node: editableControlNode, frame: editableControlFrame, completion: { [weak editableControlNode] _ in + editableControlNode?.removeFromSupernode() + }) + } + + let _ = titleApply() + + if strongSelf.backgroundNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0) + } + if strongSelf.topStripeNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1) + } + if strongSelf.bottomStripeNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2) + } + if strongSelf.maskNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.maskNode, at: 3) + } + + let hasCorners = itemListHasRoundedBlockLayout(params) + var hasTopCorners = false + var hasBottomCorners = false + switch neighbors.top { + case .sameSection(false): + strongSelf.topStripeNode.isHidden = true + default: + hasTopCorners = true + strongSelf.topStripeNode.isHidden = hasCorners + } + let bottomStripeInset: CGFloat + let bottomStripeOffset: CGFloat + switch neighbors.bottom { + case .sameSection(false): + bottomStripeInset = leftInset + editingOffset + bottomStripeOffset = -separatorHeight + default: + bottomStripeInset = 0.0 + bottomStripeOffset = 0.0 + hasBottomCorners = true + strongSelf.bottomStripeNode.isHidden = hasCorners + } + + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + + strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) + strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) + transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))) + transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))) + + transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: verticalInset + verticalOffset), size: titleLayout.size)) + + transition.updateFrame(node: strongSelf.avatarNode, frame: CGRect(origin: CGPoint(x: params.leftInset + revealOffset + editingOffset + 15.0, y: floorToScreenPixels((layout.contentSize.height - avatarSize) / 2.0)), size: CGSize(width: avatarSize, height: avatarSize))) + + if let updatedAvatarImage = updatedAvatarImage { + strongSelf.avatarNode.image = updatedAvatarImage + } + + strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel)) + + strongSelf.backgroundNode.isHidden = false + strongSelf.highlightedBackgroundNode.isHidden = true + + strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset) + + strongSelf.setRevealOptions((left: [], right: peerRevealOptions)) + strongSelf.setRevealOptionsOpened(item.isRevealed, animated: animated) + } + }) + } + } + + override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { + super.setHighlighted(highlighted, at: point, animated: animated) + + if highlighted { + self.highlightedBackgroundNode.alpha = 1.0 + if self.highlightedBackgroundNode.supernode == nil { + var anchorNode: ASDisplayNode? + if self.bottomStripeNode.supernode != nil { + anchorNode = self.bottomStripeNode + } else if self.topStripeNode.supernode != nil { + anchorNode = self.topStripeNode + } else if self.backgroundNode.supernode != nil { + anchorNode = self.backgroundNode + } + if let anchorNode = anchorNode { + self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode) + } else { + self.addSubnode(self.highlightedBackgroundNode) + } + } + } else { + if self.highlightedBackgroundNode.supernode != nil { + if animated { + self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in + if let strongSelf = self { + if completed { + strongSelf.highlightedBackgroundNode.removeFromSupernode() + } + } + }) + self.highlightedBackgroundNode.alpha = 0.0 + } else { + self.highlightedBackgroundNode.removeFromSupernode() + } + } + } + } + + override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) + } + + override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) + } + + override func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) { + super.updateRevealOffset(offset: offset, transition: transition) + + guard let _ = self.item, let params = self.layoutParams else { + return + } + + let leftInset: CGFloat + leftInset = 65.0 + params.leftInset + + let editingOffset: CGFloat + if let editableControlNode = self.editableControlNode { + editingOffset = editableControlNode.bounds.size.width + var editableControlFrame = editableControlNode.frame + editableControlFrame.origin.x = params.leftInset + offset + transition.updateFrame(node: editableControlNode, frame: editableControlFrame) + } else { + editingOffset = 0.0 + } + + transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: self.titleNode.frame.minY), size: self.titleNode.bounds.size)) + + transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: revealOffset + editingOffset + params.leftInset + 15.0, y: self.avatarNode.frame.minY), size: self.avatarNode.bounds.size)) + } + + override func revealOptionsInteractivelyOpened() { + if let item = self.item { + item.updatedRevealedOptions(true) + } + } + + override func revealOptionsInteractivelyClosed() { + if let item = self.item { + item.updatedRevealedOptions(false) + } + } + + override func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) { + self.setRevealOptionsOpened(false, animated: true) + self.revealOptionsInteractivelyClosed() + + if let item = self.item { + item.remove() + } + } + + override func header() -> ListViewItemHeader? { + return nil + } +} diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift index c9bdcc3c23..8f6ee3873e 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift @@ -6,323 +6,762 @@ import Postbox import TelegramCore import SyncCore import TelegramPresentationData +import PresentationDataUtils import ItemListUI import AccountContext import TelegramUIPreferences import ItemListPeerItem import ItemListPeerActionItem +import AvatarNode private final class ChatListFilterPresetControllerArguments { let context: AccountContext let updateState: ((ChatListFilterPresetControllerState) -> ChatListFilterPresetControllerState) -> Void - let openAddPeer: () -> Void - let deleteAdditionalPeer: (PeerId) -> Void - let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void + let openAddIncludePeer: () -> Void + let openAddExcludePeer: () -> Void + let deleteIncludePeer: (PeerId) -> Void + let deleteExcludePeer: (PeerId) -> Void + let setItemIdWithRevealedOptions: (ChatListFilterRevealedItemId?, ChatListFilterRevealedItemId?) -> Void + let deleteIncludeCategory: (ChatListFilterIncludeCategory) -> Void + let deleteExcludeCategory: (ChatListFilterExcludeCategory) -> Void + let focusOnName: () -> Void - init(context: AccountContext, updateState: @escaping ((ChatListFilterPresetControllerState) -> ChatListFilterPresetControllerState) -> Void, openAddPeer: @escaping () -> Void, deleteAdditionalPeer: @escaping (PeerId) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void) { + init( + context: AccountContext, + updateState: @escaping ((ChatListFilterPresetControllerState) -> ChatListFilterPresetControllerState) -> Void, + openAddIncludePeer: @escaping () -> Void, + openAddExcludePeer: @escaping () -> Void, + deleteIncludePeer: @escaping (PeerId) -> Void, + deleteExcludePeer: @escaping (PeerId) -> Void, + setItemIdWithRevealedOptions: @escaping (ChatListFilterRevealedItemId?, ChatListFilterRevealedItemId?) -> Void, + deleteIncludeCategory: @escaping (ChatListFilterIncludeCategory) -> Void, + deleteExcludeCategory: @escaping (ChatListFilterExcludeCategory) -> Void, + focusOnName: @escaping () -> Void + ) { self.context = context self.updateState = updateState - self.openAddPeer = openAddPeer - self.deleteAdditionalPeer = deleteAdditionalPeer - self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions + self.openAddIncludePeer = openAddIncludePeer + self.openAddExcludePeer = openAddExcludePeer + self.deleteIncludePeer = deleteIncludePeer + self.deleteExcludePeer = deleteExcludePeer + self.setItemIdWithRevealedOptions = setItemIdWithRevealedOptions + self.deleteIncludeCategory = deleteIncludeCategory + self.deleteExcludeCategory = deleteExcludeCategory + self.focusOnName = focusOnName } } private enum ChatListFilterPresetControllerSection: Int32 { + case screenHeader case name - case categories - case excludeCategories - case additionalPeers -} - -private func filterEntry(presentationData: ItemListPresentationData, arguments: ChatListFilterPresetControllerArguments, title: String, value: Bool, filter: ChatListFilterPeerCategories, section: Int32) -> ItemListCheckboxItem { - return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: section, action: { - arguments.updateState { current in - var state = current - if state.includeCategories.contains(filter) { - state.includeCategories.remove(filter) - } else { - state.includeCategories.insert(filter) - } - return state - } - }) + case includePeers + case excludePeers } private enum ChatListFilterPresetEntryStableId: Hashable { case index(Int) case peer(PeerId) - case additionalPeerInfo + case includePeerInfo + case excludePeerInfo + case includeCategory(ChatListFilterIncludeCategory) + case excludeCategory(ChatListFilterExcludeCategory) +} + +private enum ChatListFilterPresetEntrySortId: Comparable { + case screenHeader + case topIndex(Int) + case includeIndex(Int) + case excludeIndex(Int) + + static func <(lhs: ChatListFilterPresetEntrySortId, rhs: ChatListFilterPresetEntrySortId) -> Bool { + switch lhs { + case .screenHeader: + switch rhs { + case .screenHeader: + return false + default: + return true + } + case let .topIndex(lhsIndex): + switch rhs { + case .screenHeader: + return false + case let .topIndex(rhsIndex): + return lhsIndex < rhsIndex + case .includeIndex: + return true + case .excludeIndex: + return true + } + case let .includeIndex(lhsIndex): + switch rhs { + case .screenHeader: + return false + case .topIndex: + return false + case let .includeIndex(rhsIndex): + return lhsIndex < rhsIndex + case .excludeIndex: + return true + } + case let .excludeIndex(lhsIndex): + switch rhs { + case .screenHeader: + return false + case .topIndex: + return false + case .includeIndex: + return false + case let .excludeIndex(rhsIndex): + return lhsIndex < rhsIndex + } + } + } +} + +private enum ChatListFilterIncludeCategory: Int32, CaseIterable { + case contacts + case nonContacts + case groups + case channels + case bots + + var category: ChatListFilterPeerCategories { + switch self { + case .contacts: + return .contacts + case .nonContacts: + return .nonContacts + case .groups: + return .groups + case .channels: + return .channels + case .bots: + return .bots + } + } + + func title(strings: PresentationStrings) -> String { + switch self { + case .contacts: + return "Contacts" + case .nonContacts: + return "Non-Contacts" + case .groups: + return "Groups" + case .channels: + return "Channels" + case .bots: + return "Bots" + } + } +} + +private enum ChatListFilterExcludeCategory: Int32, CaseIterable { + case muted + case read + case archived + + func title(strings: PresentationStrings) -> String { + switch self { + case .muted: + return "Muted" + case .read: + return "Read" + case .archived: + return "Archived" + } + } +} + +private extension ChatListFilterCategoryIcon { + init(category: ChatListFilterIncludeCategory) { + switch category { + case .contacts: + self = .contacts + case .nonContacts: + self = .nonContacts + case .groups: + self = .groups + case .channels: + self = .channels + case .bots: + self = .bots + } + } + + init(category: ChatListFilterExcludeCategory) { + switch category { + case .muted: + self = .muted + case .read: + self = .read + case .archived: + self = .archived + } + } +} + +private enum ChatListFilterRevealedItemId: Equatable { + case peer(PeerId) + case includeCategory(ChatListFilterIncludeCategory) + case excludeCategory(ChatListFilterExcludeCategory) } private enum ChatListFilterPresetEntry: ItemListNodeEntry { + case screenHeader case nameHeader(String) case name(placeholder: String, value: String) - case typesHeader(text: String) - case filterPrivateChats(title: String, value: Bool) - case filterSecretChats(title: String, value: Bool) - case filterPrivateGroups(title: String, value: Bool) - case filterBots(title: String, value: Bool) - case filterPublicGroups(title: String, value: Bool) - case filterChannels(title: String, value: Bool) - case filterMuted(title: String, value: Bool) - case filterRead(title: String, value: Bool) - case additionalPeersHeader(String) - case addAdditionalPeer(title: String) - case additionalPeer(index: Int, peer: RenderedPeer, isRevealed: Bool) - case additionalPeerInfo(String) + case includePeersHeader(String) + case addIncludePeer(title: String) + case includeCategory(index: Int, category: ChatListFilterIncludeCategory, title: String, isRevealed: Bool) + case includePeer(index: Int, peer: RenderedPeer, isRevealed: Bool) + case includePeerInfo(String) + case excludePeersHeader(String) + case addExcludePeer(title: String) + case excludeCategory(index: Int, category: ChatListFilterExcludeCategory, title: String, isRevealed: Bool) + case excludePeer(index: Int, peer: RenderedPeer, isRevealed: Bool) + case excludePeerInfo(String) var section: ItemListSectionId { switch self { + case .screenHeader: + return ChatListFilterPresetControllerSection.screenHeader.rawValue case .nameHeader, .name: return ChatListFilterPresetControllerSection.name.rawValue - case .typesHeader, .filterPrivateChats, .filterSecretChats, .filterPrivateGroups, .filterBots, .filterPublicGroups, .filterChannels: - return ChatListFilterPresetControllerSection.categories.rawValue - case .filterMuted, .filterRead: - return ChatListFilterPresetControllerSection.excludeCategories.rawValue - case .additionalPeersHeader, .addAdditionalPeer, .additionalPeer, .additionalPeerInfo: - return ChatListFilterPresetControllerSection.additionalPeers.rawValue + case .includePeersHeader, .addIncludePeer, .includeCategory, .includePeer, .includePeerInfo: + return ChatListFilterPresetControllerSection.includePeers.rawValue + case .excludePeersHeader, .addExcludePeer, .excludeCategory, .excludePeer, .excludePeerInfo: + return ChatListFilterPresetControllerSection.excludePeers.rawValue } } var stableId: ChatListFilterPresetEntryStableId { switch self { - case .nameHeader: + case .screenHeader: return .index(0) - case .name: + case .nameHeader: return .index(1) - case .typesHeader: + case .name: return .index(2) - case .filterPrivateChats: + case .includePeersHeader: return .index(3) - case .filterSecretChats: + case .addIncludePeer: return .index(4) - case .filterPrivateGroups: + case let .includeCategory(includeCategory): + return .includeCategory(includeCategory.category) + case .includePeerInfo: return .index(5) - case .filterPublicGroups: + case .excludePeersHeader: return .index(6) - case .filterChannels: + case .addExcludePeer: return .index(7) - case .filterBots: + case let .excludeCategory(excludeCategory): + return .excludeCategory(excludeCategory.category) + case .excludePeerInfo: return .index(8) - case .filterMuted: - return .index(9) - case .filterRead: - return .index(10) - case .additionalPeersHeader: - return .index(11) - case .addAdditionalPeer: - return .index(12) - case let .additionalPeer(additionalPeer): - return .peer(additionalPeer.peer.peerId) - case .additionalPeerInfo: - return .additionalPeerInfo + case let .includePeer(peer): + return .peer(peer.peer.peerId) + case let .excludePeer(peer): + return .peer(peer.peer.peerId) + } + } + + private var sortIndex: ChatListFilterPresetEntrySortId { + switch self { + case .screenHeader: + return .screenHeader + case .nameHeader: + return .topIndex(0) + case .name: + return .topIndex(1) + case .includePeersHeader: + return .includeIndex(0) + case .addIncludePeer: + return .includeIndex(1) + case let .includeCategory(includeCategory): + return .includeIndex(2 + includeCategory.index) + case let .includePeer(includePeer): + return .includeIndex(200 + includePeer.index) + case .includePeerInfo: + return .includeIndex(1000) + case .excludePeersHeader: + return .excludeIndex(0) + case .addExcludePeer: + return .excludeIndex(1) + case let .excludeCategory(excludeCategory): + return .excludeIndex(2 + excludeCategory.index) + case let .excludePeer(excludePeer): + return .excludeIndex(200 + excludePeer.index) + case .excludePeerInfo: + return .excludeIndex(1000) } } static func <(lhs: ChatListFilterPresetEntry, rhs: ChatListFilterPresetEntry) -> Bool { - switch lhs.stableId { - case let .index(lhsIndex): - switch rhs.stableId { - case let .index(rhsIndex): - return lhsIndex < rhsIndex - case .peer: - return true - case .additionalPeerInfo: - return true - } - case .peer: - switch lhs { - case let .additionalPeer(lhsIndex, _, _): - switch rhs.stableId { - case .index: - return false - case .additionalPeerInfo: - return true - case .peer: - switch rhs { - case let .additionalPeer(rhsIndex, _, _): - return lhsIndex < rhsIndex - default: - preconditionFailure() - } - } - default: - preconditionFailure() - } - case .additionalPeerInfo: - switch rhs.stableId { - case .index: - return false - case .peer: - return false - case .additionalPeerInfo: - return false - } - } + return lhs.sortIndex < rhs.sortIndex } func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { let arguments = arguments as! ChatListFilterPresetControllerArguments switch self { + case .screenHeader: + return ChatListFilterSettingsHeaderItem(theme: presentationData.theme, text: "", animation: .newFolder, sectionId: self.section) case let .nameHeader(title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .name(placeholder, value): - return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(), text: value, placeholder: placeholder, type: .regular(capitalization: true, autocorrection: false), sectionId: self.section, textUpdated: { value in + return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(), text: value, placeholder: placeholder, type: .regular(capitalization: true, autocorrection: false), clearType: .always, maxLength: 20, sectionId: self.section, textUpdated: { value in arguments.updateState { current in var state = current state.name = value + state.changedName = true return state } - }, action: {}) - case let .typesHeader(text): + }, action: {}, cleared: { + arguments.focusOnName() + }) + case .includePeersHeader(let text), .excludePeersHeader(let text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) - case let .filterPrivateChats(title, value): - return filterEntry(presentationData: presentationData, arguments: arguments, title: title, value: value, filter: .privateChats, section: self.section) - case let .filterSecretChats(title, value): - return filterEntry(presentationData: presentationData, arguments: arguments, title: title, value: value, filter: .secretChats, section: self.section) - case let .filterPrivateGroups(title, value): - return filterEntry(presentationData: presentationData, arguments: arguments, title: title, value: value, filter: .privateGroups, section: self.section) - case let .filterBots(title, value): - return filterEntry(presentationData: presentationData, arguments: arguments, title: title, value: value, filter: .bots, section: self.section) - case let .filterPublicGroups(title, value): - return filterEntry(presentationData: presentationData, arguments: arguments, title: title, value: value, filter: .publicGroups, section: self.section) - case let .filterChannels(title, value): - return filterEntry(presentationData: presentationData, arguments: arguments, title: title, value: value, filter: .channels, section: self.section) - case let .filterMuted(title, value): - return ItemListSwitchItem(presentationData: presentationData, title: title, value: value, sectionId: self.section, style: .blocks, updated: { _ in - arguments.updateState { current in - var state = current - state.excludeMuted = !state.excludeMuted - return state - } - }) - case let .filterRead(title, value): - return ItemListSwitchItem(presentationData: presentationData, title: title, value: value, sectionId: self.section, style: .blocks, updated: { _ in - arguments.updateState { current in - var state = current - state.excludeRead = !state.excludeRead - return state - } - }) - case let .additionalPeersHeader(title): - return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) - case let .addAdditionalPeer(title): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addPersonIcon(presentationData.theme), title: title, alwaysPlain: false, sectionId: self.section, height: .peerList, editing: false, action: { - arguments.openAddPeer() - }) - case let .additionalPeer(title, peer, isRevealed): - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .monthFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: "."), nameDisplayOrder: .firstLast, context: arguments.context, peer: peer.chatMainPeer!, height: .peerList, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: isRevealed), revealOptions: ItemListPeerItemRevealOptions(options: [ItemListPeerItemRevealOption(type: .destructive, title: presentationData.strings.Common_Delete, action: { - arguments.deleteAdditionalPeer(peer.peerId) - })]), switchValue: nil, enabled: true, selectable: false, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { lhs, rhs in - arguments.setPeerIdWithRevealedOptions(lhs, rhs) - }, removePeer: { id in - arguments.deleteAdditionalPeer(id) - }) - case let .additionalPeerInfo(text): + case .includePeerInfo(let text), .excludePeerInfo(let text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) + case let .addIncludePeer(title): + return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(presentationData.theme), title: title, alwaysPlain: false, sectionId: self.section, height: .peerList, editing: false, action: { + arguments.openAddIncludePeer() + }) + case let .includeCategory(_, category, title, isRevealed): + return ChatListFilterPresetCategoryItem( + presentationData: presentationData, + title: title, + icon: ChatListFilterCategoryIcon(category: category), + isRevealed: isRevealed, + sectionId: self.section, + updatedRevealedOptions: { reveal in + if reveal { + arguments.setItemIdWithRevealedOptions(.includeCategory(category), nil) + } else { + arguments.setItemIdWithRevealedOptions(nil, .includeCategory(category)) + } + }, + remove: { + arguments.deleteIncludeCategory(category) + } + ) + case let .addExcludePeer(title): + return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(presentationData.theme), title: title, alwaysPlain: false, sectionId: self.section, height: .peerList, editing: false, action: { + arguments.openAddExcludePeer() + }) + case let .excludeCategory(_, category, title, isRevealed): + return ChatListFilterPresetCategoryItem( + presentationData: presentationData, + title: title, + icon: ChatListFilterCategoryIcon(category: category), + isRevealed: isRevealed, + sectionId: self.section, + updatedRevealedOptions: { reveal in + if reveal { + arguments.setItemIdWithRevealedOptions(.excludeCategory(category), nil) + } else { + arguments.setItemIdWithRevealedOptions(nil, .excludeCategory(category)) + } + }, + remove: { + arguments.deleteExcludeCategory(category) + } + ) + case let .includePeer(_, peer, isRevealed): + return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .monthFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: "."), nameDisplayOrder: .firstLast, context: arguments.context, peer: peer.chatMainPeer!, height: .peerList, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: isRevealed), revealOptions: ItemListPeerItemRevealOptions(options: [ItemListPeerItemRevealOption(type: .destructive, title: presentationData.strings.Common_Delete, action: { + arguments.deleteIncludePeer(peer.peerId) + })]), switchValue: nil, enabled: true, selectable: false, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { lhs, rhs in + arguments.setItemIdWithRevealedOptions(lhs.flatMap { .peer($0) }, rhs.flatMap { .peer($0) }) + }, removePeer: { id in + arguments.deleteIncludePeer(id) + }) + case let .excludePeer(_, peer, isRevealed): + return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .monthFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: "."), nameDisplayOrder: .firstLast, context: arguments.context, peer: peer.chatMainPeer!, height: .peerList, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: isRevealed), revealOptions: ItemListPeerItemRevealOptions(options: [ItemListPeerItemRevealOption(type: .destructive, title: presentationData.strings.Common_Delete, action: { + arguments.deleteExcludePeer(peer.peerId) + })]), switchValue: nil, enabled: true, selectable: false, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { lhs, rhs in + arguments.setItemIdWithRevealedOptions(lhs.flatMap { .peer($0) }, rhs.flatMap { .peer($0) }) + }, removePeer: { id in + arguments.deleteExcludePeer(id) + }) } } } private struct ChatListFilterPresetControllerState: Equatable { var name: String + var changedName: Bool var includeCategories: ChatListFilterPeerCategories var excludeMuted: Bool var excludeRead: Bool + var excludeArchived: Bool var additionallyIncludePeers: [PeerId] + var additionallyExcludePeers: [PeerId] - var revealedPeerId: PeerId? + var revealedItemId: ChatListFilterRevealedItemId? var isComplete: Bool { if self.name.isEmpty { return false } - if self.includeCategories.isEmpty && self.additionallyIncludePeers.isEmpty && !self.excludeMuted && !self.excludeRead { + + let defaultCategories: ChatListFilterPeerCategories = .all + let defaultExcludeArchived = true + let defaultExcludeMuted = false + let defaultExcludeRead = false + + if self.includeCategories == defaultCategories && + self.excludeArchived == defaultExcludeArchived && + self.excludeMuted == defaultExcludeMuted && + self.excludeRead == defaultExcludeRead { + return false + } + + if self.includeCategories.isEmpty && self.additionallyIncludePeers.isEmpty { return false } + return true } } //TODO:localization -private func chatListFilterPresetControllerEntries(presentationData: PresentationData, state: ChatListFilterPresetControllerState, peers: [RenderedPeer]) -> [ChatListFilterPresetEntry] { +private func chatListFilterPresetControllerEntries(presentationData: PresentationData, isNewFilter: Bool, state: ChatListFilterPresetControllerState, includePeers: [RenderedPeer], excludePeers: [RenderedPeer]) -> [ChatListFilterPresetEntry] { var entries: [ChatListFilterPresetEntry] = [] - entries.append(.nameHeader("FILTER NAME")) - entries.append(.name(placeholder: "Filter Name", value: state.name)) - - entries.append(.typesHeader(text: "INCLUDE CHAT TYPES")) - entries.append(.filterPrivateChats(title: "Private Chats", value: state.includeCategories.contains(.privateChats))) - entries.append(.filterSecretChats(title: "Secret Chats", value: state.includeCategories.contains(.secretChats))) - entries.append(.filterPrivateGroups(title: "Private Groups", value: state.includeCategories.contains(.privateGroups))) - entries.append(.filterPublicGroups(title: "Public Groups", value: state.includeCategories.contains(.publicGroups))) - entries.append(.filterChannels(title: "Channels", value: state.includeCategories.contains(.channels))) - entries.append(.filterBots(title: "Bots", value: state.includeCategories.contains(.bots))) - - entries.append(.filterMuted(title: "Exclude Muted", value: state.excludeMuted)) - entries.append(.filterRead(title: "Exclude Read", value: state.excludeRead)) - - entries.append(.additionalPeersHeader("ALWAYS INCLUDE")) - entries.append(.addAdditionalPeer(title: "Add Chats")) - - for peer in peers { - entries.append(.additionalPeer(index: entries.count, peer: peer, isRevealed: state.revealedPeerId == peer.peerId)) + if isNewFilter { + entries.append(.screenHeader) } - entries.append(.additionalPeerInfo("These chats will always be included in the list.")) + entries.append(.nameHeader("FOLDER NAME")) + entries.append(.name(placeholder: "Folder Name", value: state.name)) + + entries.append(.includePeersHeader("INCLUDED CHATS")) + entries.append(.addIncludePeer(title: "Add Chats")) + + var includeCategoryIndex = 0 + for category in ChatListFilterIncludeCategory.allCases { + if state.includeCategories.contains(category.category) { + entries.append(.includeCategory(index: includeCategoryIndex, category: category, title: category.title(strings: presentationData.strings), isRevealed: state.revealedItemId == .includeCategory(category))) + } + includeCategoryIndex += 1 + } + + for peer in includePeers { + entries.append(.includePeer(index: entries.count, peer: peer, isRevealed: state.revealedItemId == .peer(peer.peerId))) + } + + entries.append(.includePeerInfo("Choose chats and types of chats that will appear in this filter.")) + + entries.append(.excludePeersHeader("EXCLUDED CHATS")) + entries.append(.addExcludePeer(title: "Add Chats")) + + var excludeCategoryIndex = 0 + for category in ChatListFilterExcludeCategory.allCases { + let isExcluded: Bool + switch category { + case .read: + isExcluded = state.excludeRead + case .muted: + isExcluded = state.excludeMuted + case .archived: + isExcluded = state.excludeArchived + } + + if isExcluded { + entries.append(.excludeCategory(index: excludeCategoryIndex, category: category, title: category.title(strings: presentationData.strings), isRevealed: state.revealedItemId == .excludeCategory(category))) + } + excludeCategoryIndex += 1 + } + + for peer in excludePeers { + entries.append(.excludePeer(index: entries.count, peer: peer, isRevealed: state.revealedItemId == .peer(peer.peerId))) + } + + entries.append(.excludePeerInfo("Choose chats and types of chats that will never appear in the filter.")) return entries } +private enum AdditionalCategoryId: Int { + case contacts + case nonContacts + case groups + case channels + case bots +} + +private enum AdditionalExcludeCategoryId: Int { + case muted + case read + case archived +} + func chatListFilterAddChatsController(context: AccountContext, filter: ChatListFilter) -> ViewController { - let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .peerSelection(searchChatList: true, searchGroups: true, searchChannels: true), options: [])) + return internalChatListFilterAddChatsController(context: context, filter: filter, applyAutomatically: true, updated: { _ in }) +} + +private func internalChatListFilterAddChatsController(context: AccountContext, filter: ChatListFilter, applyAutomatically: Bool, updated: @escaping (ChatListFilter) -> Void) -> ViewController { + let additionalCategories: [ChatListNodeAdditionalCategory] = [ + ChatListNodeAdditionalCategory( + id: AdditionalCategoryId.contacts.rawValue, + icon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: .white), color: .blue), + title: "Contacts" + ), + ChatListNodeAdditionalCategory( + id: AdditionalCategoryId.nonContacts.rawValue, + icon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/UnknownUser"), color: .white), color: .yellow), + title: "Non-Contacts" + ), + ChatListNodeAdditionalCategory( + id: AdditionalCategoryId.groups.rawValue, + icon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Groups"), color: .white), color: .green), + title: "Groups" + ), + ChatListNodeAdditionalCategory( + id: AdditionalCategoryId.channels.rawValue, + icon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Channels"), color: .white), color: .red), + title: "Channels" + ), + ChatListNodeAdditionalCategory( + id: AdditionalCategoryId.bots.rawValue, + icon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Bots"), color: .white), color: .violet), + title: "Bots" + ) + ] + var selectedCategories = Set() + let categoryMapping: [ChatListFilterPeerCategories: AdditionalCategoryId] = [ + .contacts: .contacts, + .nonContacts: .nonContacts, + .groups: .groups, + .channels: .channels, + .bots: .bots + ] + for (category, id) in categoryMapping { + if filter.data.categories.contains(category) { + selectedCategories.insert(id.rawValue) + } + } + + let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .chatSelection(selectedChats: Set(filter.data.includePeers), additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories)), options: [], alwaysEnabled: true)) controller.navigationPresentation = .modal let _ = (controller.result |> take(1) - |> deliverOnMainQueue).start(next: { [weak controller] peerIds in - let _ = (updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { settings in - var settings = settings - for i in 0 ..< settings.filters.count { - if settings.filters[i].id == filter.id { - let previousIncludePeers = settings.filters[i].includePeers - - var chatPeerIds: [PeerId] = [] - for peerId in peerIds { - switch peerId { - case let .peer(id): - chatPeerIds.append(id) - default: - break - } - } - settings.filters[i].includePeers = chatPeerIds + previousIncludePeers.filter { peerId in - return !chatPeerIds.contains(peerId) + |> deliverOnMainQueue).start(next: { [weak controller] result in + guard case let .result(peerIds, additionalCategoryIds) = result else { + controller?.dismiss() + return + } + + var includePeers: [PeerId] = [] + for peerId in peerIds { + switch peerId { + case let .peer(id): + includePeers.append(id) + default: + break + } + } + includePeers.sort() + + var categories: ChatListFilterPeerCategories = [] + for id in additionalCategoryIds { + if let index = categoryMapping.firstIndex(where: { $0.1.rawValue == id }) { + categories.insert(categoryMapping[index].0) + } else { + assertionFailure() + } + } + + if applyAutomatically { + let _ = (updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in + var filters = filters + for i in 0 ..< filters.count { + if filters[i].id == filter.id { + filters[i].data.categories = categories + filters[i].data.includePeers = includePeers + filters[i].data.excludePeers = filters[i].data.excludePeers.filter { !filters[i].data.includePeers.contains($0) } } } - } - return settings - }) - |> deliverOnMainQueue).start(next: { settings in + return filters + }) + |> deliverOnMainQueue).start(next: { _ in + controller?.dismiss() + }) + } else { + var filter = filter + filter.data.categories = categories + filter.data.includePeers = includePeers + filter.data.excludePeers = filter.data.excludePeers.filter { !filter.data.includePeers.contains($0) } + updated(filter) controller?.dismiss() - }) + } }) return controller } +private func internalChatListFilterExcludeChatsController(context: AccountContext, filter: ChatListFilter, applyAutomatically: Bool, updated: @escaping (ChatListFilter) -> Void) -> ViewController { + let additionalCategories: [ChatListNodeAdditionalCategory] = [ + ChatListNodeAdditionalCategory( + id: AdditionalExcludeCategoryId.muted.rawValue, + icon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Muted"), color: .white), color: .red), + title: "Muted" + ), + ChatListNodeAdditionalCategory( + id: AdditionalExcludeCategoryId.read.rawValue, + icon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MarkAsRead"), color: .white), color: .blue), + title: "Read" + ), + ChatListNodeAdditionalCategory( + id: AdditionalExcludeCategoryId.archived.rawValue, + icon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Archive"), color: .white), color: .yellow), + title: "Archived" + ), + ] + var selectedCategories = Set() + if filter.data.excludeMuted { + selectedCategories.insert(AdditionalExcludeCategoryId.muted.rawValue) + } + if filter.data.excludeRead { + selectedCategories.insert(AdditionalExcludeCategoryId.read.rawValue) + } + if filter.data.excludeArchived { + selectedCategories.insert(AdditionalExcludeCategoryId.archived.rawValue) + } + + let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .chatSelection(selectedChats: Set(filter.data.excludePeers), additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories)), options: [], alwaysEnabled: true)) + controller.navigationPresentation = .modal + let _ = (controller.result + |> take(1) + |> deliverOnMainQueue).start(next: { [weak controller] result in + guard case let .result(peerIds, additionalCategoryIds) = result else { + controller?.dismiss() + return + } + + var excludePeers: [PeerId] = [] + for peerId in peerIds { + switch peerId { + case let .peer(id): + excludePeers.append(id) + default: + break + } + } + excludePeers.sort() + + if applyAutomatically { + let _ = (updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in + var filters = filters + for i in 0 ..< filters.count { + if filters[i].id == filter.id { + filters[i].data.excludeMuted = additionalCategoryIds.contains(AdditionalExcludeCategoryId.muted.rawValue) + filters[i].data.excludeRead = additionalCategoryIds.contains(AdditionalExcludeCategoryId.read.rawValue) + filters[i].data.excludeArchived = additionalCategoryIds.contains(AdditionalExcludeCategoryId.archived.rawValue) + filters[i].data.excludePeers = excludePeers + filters[i].data.includePeers = filters[i].data.includePeers.filter { !filters[i].data.excludePeers.contains($0) } + } + } + return filters + }) + |> deliverOnMainQueue).start(next: { _ in + controller?.dismiss() + }) + } else { + var filter = filter + filter.data.excludeMuted = additionalCategoryIds.contains(AdditionalExcludeCategoryId.muted.rawValue) + filter.data.excludeRead = additionalCategoryIds.contains(AdditionalExcludeCategoryId.read.rawValue) + filter.data.excludeArchived = additionalCategoryIds.contains(AdditionalExcludeCategoryId.archived.rawValue) + filter.data.excludePeers = excludePeers + filter.data.includePeers = filter.data.includePeers.filter { !filter.data.excludePeers.contains($0) } + updated(filter) + controller?.dismiss() + } + }) + return controller +} + +enum ChatListFilterType { + case generic + case unmuted + case unread + case channels + case groups + case bots + case contacts + case nonContacts +} + +func chatListFilterType(_ filter: ChatListFilter) -> ChatListFilterType { + let filterType: ChatListFilterType + if filter.data.includePeers.isEmpty { + if filter.data.categories == .all { + if filter.data.excludeRead { + filterType = .unread + } else if filter.data.excludeMuted { + filterType = .unmuted + } else { + filterType = .generic + } + } else { + if filter.data.categories == .channels { + filterType = .channels + } else if filter.data.categories == .groups { + filterType = .groups + } else if filter.data.categories == .bots { + filterType = .bots + } else if filter.data.categories == .contacts { + filterType = .contacts + } else if filter.data.categories == .nonContacts { + filterType = .nonContacts + } else { + filterType = .generic + } + } + } else { + filterType = .generic + } + return filterType +} + func chatListFilterPresetController(context: AccountContext, currentPreset: ChatListFilter?, updated: @escaping ([ChatListFilter]) -> Void) -> ViewController { let initialName: String if let currentPreset = currentPreset { - initialName = currentPreset.title ?? "" + initialName = currentPreset.title } else { - initialName = "New Filter" + initialName = "New Folder" } - let initialState = ChatListFilterPresetControllerState(name: initialName, includeCategories: currentPreset?.categories ?? .all, excludeMuted: currentPreset?.excludeMuted ?? false, excludeRead: currentPreset?.excludeRead ?? false, additionallyIncludePeers: currentPreset?.includePeers ?? []) + let initialState = ChatListFilterPresetControllerState(name: initialName, changedName: currentPreset != nil, includeCategories: currentPreset?.data.categories ?? [], excludeMuted: currentPreset?.data.excludeMuted ?? false, excludeRead: currentPreset?.data.excludeRead ?? false, excludeArchived: currentPreset?.data.excludeArchived ?? false, additionallyIncludePeers: currentPreset?.data.includePeers ?? [], additionallyExcludePeers: currentPreset?.data.excludePeers ?? []) let stateValue = Atomic(value: initialState) let statePromise = ValuePromise(initialState, ignoreRepeated: true) let updateState: ((ChatListFilterPresetControllerState) -> ChatListFilterPresetControllerState) -> Void = { f in - statePromise.set(stateValue.modify { f($0) }) + statePromise.set(stateValue.modify { current in + var state = f(current) + if !state.changedName { + let filter = ChatListFilter(id: currentPreset?.id ?? -1, title: state.name, data: ChatListFilterData(categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: state.additionallyIncludePeers, excludePeers: state.additionallyExcludePeers)) + switch chatListFilterType(filter) { + case .generic: + state.name = initialName + case .unmuted: + state.name = "Not Muted" + case .unread: + state.name = "Unread" + case .channels: + state.name = "Channels" + case .groups: + state.name = "Groups" + case .bots: + state.name = "Bots" + case .contacts: + state.name = "Contacts" + case .nonContacts: + state.name = "Non-Contacts" + } + } + return state + }) } + var skipStateAnimation = false let actionsDisposable = DisposableSet() @@ -331,115 +770,207 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat var presentControllerImpl: ((ViewController, Any?) -> Void)? var dismissImpl: (() -> Void)? + var focusOnNameImpl: (() -> Void)? let arguments = ChatListFilterPresetControllerArguments( context: context, updateState: { f in updateState(f) }, - openAddPeer: { - let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .peerSelection(searchChatList: true, searchGroups: true, searchChannels: true), options: [])) - addPeerDisposable.set((controller.result - |> take(1) - |> deliverOnMainQueue).start(next: { [weak controller] peerIds in - controller?.dismiss() + openAddIncludePeer: { + let state = stateValue.with { $0 } + let filter = ChatListFilter(id: currentPreset?.id ?? -1, title: state.name, data: ChatListFilterData(categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: state.additionallyIncludePeers, excludePeers: state.additionallyExcludePeers)) + + let controller = internalChatListFilterAddChatsController(context: context, filter: filter, applyAutomatically: false, updated: { filter in + skipStateAnimation = true updateState { state in var state = state - for peerId in peerIds { - switch peerId { - case let .peer(id): - if !state.additionallyIncludePeers.contains(id) { - state.additionallyIncludePeers.insert(id, at: 0) - } - default: - break - } - } + state.additionallyIncludePeers = filter.data.includePeers + state.additionallyExcludePeers = filter.data.excludePeers + state.includeCategories = filter.data.categories return state } - })) + }) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) }, - deleteAdditionalPeer: { peerId in + openAddExcludePeer: { + let state = stateValue.with { $0 } + let filter = ChatListFilter(id: currentPreset?.id ?? -1, title: state.name, data: ChatListFilterData(categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: state.additionallyIncludePeers, excludePeers: state.additionallyExcludePeers)) + + let controller = internalChatListFilterExcludeChatsController(context: context, filter: filter, applyAutomatically: false, updated: { filter in + skipStateAnimation = true + updateState { state in + var state = state + state.additionallyIncludePeers = filter.data.includePeers + state.additionallyExcludePeers = filter.data.excludePeers + state.includeCategories = filter.data.categories + state.excludeRead = filter.data.excludeRead + state.excludeMuted = filter.data.excludeMuted + state.excludeArchived = filter.data.excludeArchived + return state + } + }) + presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + }, + deleteIncludePeer: { peerId in updateState { state in var state = state - if let index = state.additionallyIncludePeers.index(of: peerId) { + if let index = state.additionallyIncludePeers.firstIndex(of: peerId) { state.additionallyIncludePeers.remove(at: index) } return state } }, - setPeerIdWithRevealedOptions: { peerId, fromPeerId in + deleteExcludePeer: { peerId in updateState { state in var state = state - if (peerId == nil && fromPeerId == state.revealedPeerId) || (peerId != nil && fromPeerId == nil) { - state.revealedPeerId = peerId + if let index = state.additionallyExcludePeers.firstIndex(of: peerId) { + state.additionallyExcludePeers.remove(at: index) } return state } + }, + setItemIdWithRevealedOptions: { itemId, fromItemId in + updateState { state in + var state = state + if (itemId == nil && fromItemId == state.revealedItemId) || (itemId != nil && fromItemId == nil) { + state.revealedItemId = itemId + } + return state + } + }, + deleteIncludeCategory: { category in + updateState { state in + var state = state + state.includeCategories.remove(category.category) + return state + } + }, + deleteExcludeCategory: { category in + updateState { state in + var state = state + switch category { + case .muted: + state.excludeMuted = false + case .read: + state.excludeRead = false + case .archived: + state.excludeArchived = false + } + return state + } + }, + focusOnName: { + focusOnNameImpl?() } ) - let statePeers = statePromise.get() - |> map { state -> [PeerId] in - return state.additionallyIncludePeers - } - |> distinctUntilChanged - |> mapToSignal { peerIds -> Signal<[RenderedPeer], NoError> in - return context.account.postbox.transaction { transaction -> [RenderedPeer] in - var result: [RenderedPeer] = [] - for peerId in peerIds { - if let peer = transaction.getPeer(peerId) { - result.append(RenderedPeer(peer: peer)) - } + let currentPeers = Atomic<[PeerId: RenderedPeer]>(value: [:]) + let stateWithPeers = statePromise.get() + |> mapToSignal { state -> Signal<(ChatListFilterPresetControllerState, [RenderedPeer], [RenderedPeer]), NoError> in + let currentPeersValue = currentPeers.with { $0 } + var included: [RenderedPeer] = [] + var excluded: [RenderedPeer] = [] + var missingPeers = false + for peerId in state.additionallyIncludePeers { + if let peer = currentPeersValue[peerId] { + included.append(peer) + } else { + missingPeers = true } - return result } + for peerId in state.additionallyExcludePeers { + if let peer = currentPeersValue[peerId] { + excluded.append(peer) + } else { + missingPeers = true + } + } + if missingPeers { + return context.account.postbox.transaction { transaction -> (ChatListFilterPresetControllerState, [RenderedPeer], [RenderedPeer]) in + var included: [RenderedPeer] = [] + var excluded: [RenderedPeer] = [] + var allPeers: [PeerId: RenderedPeer] = [:] + for peerId in state.additionallyIncludePeers { + if let peer = transaction.getPeer(peerId) { + let renderedPeer = RenderedPeer(peer: peer) + included.append(renderedPeer) + allPeers[renderedPeer.peerId] = renderedPeer + } + } + for peerId in state.additionallyExcludePeers { + if let peer = transaction.getPeer(peerId) { + let renderedPeer = RenderedPeer(peer: peer) + excluded.append(renderedPeer) + allPeers[renderedPeer.peerId] = renderedPeer + } + } + let _ = currentPeers.swap(allPeers) + return (state, included, excluded) + } + } else { + return .single((state, included, excluded)) + } + } + + var attemptNavigationImpl: (() -> Bool)? + var applyImpl: (() -> Void)? = { + let state = stateValue.with { $0 } + let preset = ChatListFilter(id: currentPreset?.id ?? -1, title: state.name, data: ChatListFilterData(categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: state.additionallyIncludePeers, excludePeers: state.additionallyExcludePeers)) + let _ = (updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in + var preset = preset + if currentPreset == nil { + preset.id = max(2, filters.map({ $0.id + 1 }).max() ?? 2) + } + var filters = filters + if let _ = currentPreset { + var found = false + for i in 0 ..< filters.count { + if filters[i].id == preset.id { + filters[i] = preset + found = true + } + } + if !found { + filters = filters.filter { listFilter in + if listFilter.title == preset.title && listFilter.data == preset.data { + return false + } + return true + } + filters.append(preset) + } + } else { + filters.append(preset) + } + return filters + }) + |> deliverOnMainQueue).start(next: { filters in + updated(filters) + dismissImpl?() + }) } let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, - statePromise.get(), - statePeers + stateWithPeers ) |> deliverOnMainQueue - |> map { presentationData, state, statePeers -> (ItemListControllerState, (ItemListNodeState, Any)) in + |> map { presentationData, stateWithPeers -> (ItemListControllerState, (ItemListNodeState, Any)) in + let (state, includePeers, excludePeers) = stateWithPeers + let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { - dismissImpl?() + if attemptNavigationImpl?() ?? true { + dismissImpl?() + } }) let rightNavigationButton = ItemListNavigationButton(content: .text(currentPreset == nil ? presentationData.strings.Common_Create : presentationData.strings.Common_Done), style: .bold, enabled: state.isComplete, action: { - let state = stateValue.with { $0 } - let preset = ChatListFilter(id: currentPreset?.id ?? -1, title: state.name, categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, includePeers: state.additionallyIncludePeers) - let _ = (updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { settings in - var preset = preset - if currentPreset == nil { - preset.id = max(2, settings.filters.map({ $0.id + 1 }).max() ?? 2) - } - var settings = settings - if let currentPreset = currentPreset { - var found = false - for i in 0 ..< settings.filters.count { - if settings.filters[i].id == preset.id { - settings.filters[i] = preset - found = true - } - } - if !found { - settings.filters.append(preset) - } - } else { - settings.filters.append(preset) - } - return settings - }) - |> deliverOnMainQueue).start(next: { settings in - updated(settings.filters) - dismissImpl?() - }) + applyImpl?() }) - let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(currentPreset != nil ? "Filter" : "Create Filter"), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: chatListFilterPresetControllerEntries(presentationData: presentationData, state: state, peers: statePeers), style: .blocks, emptyStateItem: nil, animateChanges: true) + let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(currentPreset != nil ? "Edit Folder" : "Create Folder"), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: chatListFilterPresetControllerEntries(presentationData: presentationData, isNewFilter: currentPreset == nil, state: state, includePeers: includePeers, excludePeers: excludePeers), style: .blocks, emptyStateItem: nil, animateChanges: !skipStateAnimation) + skipStateAnimation = false return (controllerState, (listState, arguments)) } @@ -455,6 +986,45 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat dismissImpl = { [weak controller] in let _ = controller?.dismiss() } + focusOnNameImpl = { [weak controller] in + guard let controller = controller else { + return + } + controller.forEachItemNode { itemNode in + if let itemNode = itemNode as? ItemListSingleLineInputItemNode { + itemNode.focus() + } + } + } + controller.attemptNavigation = { _ in + return attemptNavigationImpl?() ?? true + } + let displaySaveAlert: () -> Void = { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + presentControllerImpl?(textAlertController(context: context, title: nil, text: "You have changed the filter. Apply changes?", actions: [ + TextAlertAction(type: .genericAction, title: "Discard", action: { + dismissImpl?() + }), + TextAlertAction(type: .defaultAction, title: "Apply", action: { + applyImpl?() + })]), nil) + } + attemptNavigationImpl = { + let state = stateValue.with { $0 } + if let currentPreset = currentPreset { + let filter = ChatListFilter(id: currentPreset.id, title: state.name, data: ChatListFilterData(categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: state.additionallyIncludePeers, excludePeers: state.additionallyExcludePeers)) + if currentPreset != filter { + displaySaveAlert() + return false + } + } else { + if state.isComplete { + displaySaveAlert() + return false + } + } + return true + } return controller } diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift index 53b5d67994..398b2eb674 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift @@ -14,13 +14,15 @@ import ItemListPeerActionItem private final class ChatListFilterPresetListControllerArguments { let context: AccountContext + let addSuggestedPresed: (String, ChatListFilterData) -> Void let openPreset: (ChatListFilter) -> Void let addNew: () -> Void let setItemWithRevealedOptions: (Int32?, Int32?) -> Void let removePreset: (Int32) -> Void - init(context: AccountContext, openPreset: @escaping (ChatListFilter) -> Void, addNew: @escaping () -> Void, setItemWithRevealedOptions: @escaping (Int32?, Int32?) -> Void, removePreset: @escaping (Int32) -> Void) { + init(context: AccountContext, addSuggestedPresed: @escaping (String, ChatListFilterData) -> Void, openPreset: @escaping (ChatListFilter) -> Void, addNew: @escaping () -> Void, setItemWithRevealedOptions: @escaping (Int32?, Int32?) -> Void, removePreset: @escaping (Int32) -> Void) { self.context = context + self.addSuggestedPresed = addSuggestedPresed self.openPreset = openPreset self.addNew = addNew self.setItemWithRevealedOptions = setItemWithRevealedOptions @@ -29,6 +31,8 @@ private final class ChatListFilterPresetListControllerArguments { } private enum ChatListFilterPresetListSection: Int32 { + case screenHeader + case suggested case list } @@ -45,20 +49,40 @@ private func stringForUserCount(_ peers: [PeerId: SelectivePrivacyPeer], strings } private enum ChatListFilterPresetListEntryStableId: Hashable { + case screenHeader + case suggestedListHeader + case suggestedPreset(ChatListFilterData) + case suggestedAddCustom case listHeader case preset(Int32) case addItem case listFooter } +private struct PresetIndex: Equatable { + let value: Int + + static func ==(lhs: PresetIndex, rhs: PresetIndex) -> Bool { + return true + } +} + private enum ChatListFilterPresetListEntry: ItemListNodeEntry { + case screenHeader(String) + case suggestedListHeader(String) + case suggestedPreset(index: PresetIndex, title: String, label: String, preset: ChatListFilterData) + case suggestedAddCustom(String) case listHeader(String) - case preset(index: Int, title: String?, preset: ChatListFilter, canBeReordered: Bool, canBeDeleted: Bool, isEditing: Bool) + case preset(index: PresetIndex, title: String, label: String, preset: ChatListFilter, canBeReordered: Bool, canBeDeleted: Bool, isEditing: Bool) case addItem(text: String, isEditing: Bool) case listFooter(String) var section: ItemListSectionId { switch self { + case .screenHeader: + return ChatListFilterPresetListSection.screenHeader.rawValue + case .suggestedListHeader, .suggestedPreset, .suggestedAddCustom: + return ChatListFilterPresetListSection.suggested.rawValue case .listHeader, .preset, .addItem, .listFooter: return ChatListFilterPresetListSection.list.rawValue } @@ -66,19 +90,35 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry { var sortId: Int { switch self { + case .screenHeader: + return 0 case .listHeader: - return 2 + return 100 case let .preset(preset): - return 3 + preset.index + return 101 + preset.index.value case .addItem: return 1000 case .listFooter: return 1001 + case .suggestedListHeader: + return 1002 + case let .suggestedPreset(suggestedPreset): + return 1003 + suggestedPreset.index.value + case .suggestedAddCustom: + return 2000 } } var stableId: ChatListFilterPresetListEntryStableId { switch self { + case .screenHeader: + return .screenHeader + case .suggestedListHeader: + return .suggestedListHeader + case let .suggestedPreset(suggestedPreset): + return .suggestedPreset(suggestedPreset.preset) + case .suggestedAddCustom: + return .suggestedAddCustom case .listHeader: return .listHeader case let .preset(preset): @@ -97,10 +137,22 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry { func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { let arguments = arguments as! ChatListFilterPresetListControllerArguments switch self { + case let .screenHeader(text): + return ChatListFilterSettingsHeaderItem(theme: presentationData.theme, text: text, animation: .folders, sectionId: self.section) + case let .suggestedListHeader(text): + return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section) + case let .suggestedPreset(_, title, label, preset): + return ChatListFilterPresetListSuggestedItem(presentationData: presentationData, title: title, label: label, sectionId: self.section, style: .blocks, installAction: { + arguments.addSuggestedPresed(title, preset) + }, tag: nil) + case let .suggestedAddCustom(text): + return ItemListPeerActionItem(presentationData: presentationData, icon: nil, title: text, sectionId: self.section, height: .generic, editing: false, action: { + arguments.addNew() + }) case let .listHeader(text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section) - case let .preset(index, title, preset, canBeReordered, canBeDeleted, isEditing): - return ChatListFilterPresetListItem(presentationData: presentationData, preset: preset, title: title ?? "", editing: ChatListFilterPresetListItemEditing(editable: true, editing: isEditing, revealed: false), canBeReordered: canBeReordered, canBeDeleted: canBeDeleted, sectionId: self.section, action: { + case let .preset(_, title, label, preset, canBeReordered, canBeDeleted, isEditing): + return ChatListFilterPresetListItem(presentationData: presentationData, preset: preset, title: title, label: label, editing: ChatListFilterPresetListItemEditing(editable: true, editing: isEditing, revealed: false), canBeReordered: canBeReordered, canBeDeleted: canBeDeleted, sectionId: self.section, action: { arguments.openPreset(preset) }, setItemWithRevealedOptions: { lhs, rhs in arguments.setItemWithRevealedOptions(lhs, rhs) @@ -122,22 +174,68 @@ private struct ChatListFilterPresetListControllerState: Equatable { var revealedPreset: Int32? = nil } -private func chatListFilterPresetListControllerEntries(presentationData: PresentationData, state: ChatListFilterPresetListControllerState, filtersState: ChatListFiltersState, settings: ChatListFilterSettings) -> [ChatListFilterPresetListEntry] { +private func filtersWithAppliedOrder(filters: [(ChatListFilter, Int)], order: [Int32]?) -> [(ChatListFilter, Int)] { + let sortedFilters: [(ChatListFilter, Int)] + if let updatedFilterOrder = order { + var updatedFilters: [(ChatListFilter, Int)] = [] + for id in updatedFilterOrder { + if let index = filters.firstIndex(where: { $0.0.id == id }) { + updatedFilters.append(filters[index]) + } + } + sortedFilters = updatedFilters + } else { + sortedFilters = filters + } + return sortedFilters +} + +private func chatListFilterPresetListControllerEntries(presentationData: PresentationData, state: ChatListFilterPresetListControllerState, filters: [(ChatListFilter, Int)], updatedFilterOrder: [Int32]?, suggestedFilters: [ChatListFeaturedFilter], settings: ChatListFilterSettings) -> [ChatListFilterPresetListEntry] { var entries: [ChatListFilterPresetListEntry] = [] - entries.append(.listHeader("FILTERS")) - for preset in filtersState.filters { - entries.append(.preset(index: entries.count, title: preset.title, preset: preset, canBeReordered: filtersState.filters.count > 1, canBeDeleted: true, isEditing: state.isEditing)) + + entries.append(.screenHeader("Create folders for different groups of chats and\nquickly switch between them.")) + + let filteredSuggestedFilters = suggestedFilters.filter { suggestedFilter in + for (filter, _) in filters { + if filter.data == suggestedFilter.data { + return false + } + } + return true } - if filtersState.filters.count < 10 { - entries.append(.addItem(text: "Create New Filter", isEditing: state.isEditing)) + + if !filters.isEmpty || suggestedFilters.isEmpty { + entries.append(.listHeader("FOLDERS")) + + for (filter, chatCount) in filtersWithAppliedOrder(filters: filters, order: updatedFilterOrder) { + entries.append(.preset(index: PresetIndex(value: entries.count), title: filter.title, label: chatCount == 0 ? "" : "\(chatCount)", preset: filter, canBeReordered: filters.count > 1, canBeDeleted: true, isEditing: state.isEditing)) + } + if filters.count < 10 { + entries.append(.addItem(text: "Create New Folder", isEditing: state.isEditing)) + } + entries.append(.listFooter("Tap \"Edit\" to change the order or delete folders.")) + } + + if !filteredSuggestedFilters.isEmpty && filters.count < 10 { + entries.append(.suggestedListHeader("RECOMMENDED FOLDERS")) + for filter in filteredSuggestedFilters { + entries.append(.suggestedPreset(index: PresetIndex(value: entries.count), title: filter.title, label: filter.description, preset: filter.data)) + } + if filters.isEmpty { + entries.append(.suggestedAddCustom("Add Custom Folder")) + } } - entries.append(.listFooter("Tap \"Edit\" to change the order or delete filters.")) return entries } -func chatListFilterPresetListController(context: AccountContext, updated: @escaping ([ChatListFilter]) -> Void) -> ViewController { +public enum ChatListFilterPresetListControllerMode { + case `default` + case modal +} + +public func chatListFilterPresetListController(context: AccountContext, mode: ChatListFilterPresetListControllerMode, dismissed: (() -> Void)? = nil) -> ViewController { let initialState = ChatListFilterPresetListControllerState() let statePromise = ValuePromise(initialState, ignoreRepeated: true) let stateValue = Atomic(value: initialState) @@ -147,12 +245,21 @@ func chatListFilterPresetListController(context: AccountContext, updated: @escap var dismissImpl: (() -> Void)? var pushControllerImpl: ((ViewController) -> Void)? - var presentControllerImpl: ((ViewController, Any?) -> Void)? - let arguments = ChatListFilterPresetListControllerArguments(context: context, openPreset: { preset in - pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: preset, updated: updated)) + let arguments = ChatListFilterPresetListControllerArguments(context: context, + addSuggestedPresed: { title, data in + let _ = (updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in + var filters = filters + let id = generateNewChatListFilterId(filters: filters) + filters.insert(ChatListFilter(id: id, title: title, data: data), at: 0) + return filters + }) + |> deliverOnMainQueue).start(next: { _ in + }) + }, openPreset: { preset in + pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: preset, updated: { _ in })) }, addNew: { - pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: nil, updated: updated)) + pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: nil, updated: { _ in })) }, setItemWithRevealedOptions: { preset, fromPreset in updateState { state in var state = state @@ -162,42 +269,126 @@ func chatListFilterPresetListController(context: AccountContext, updated: @escap return state } }, removePreset: { id in - let _ = (updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { settings in - var settings = settings - if let index = settings.filters.index(where: { $0.id == id }) { - settings.filters.remove(at: index) + let _ = (updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in + var filters = filters + if let index = filters.firstIndex(where: { $0.id == id }) { + filters.remove(at: index) } - return settings + return filters }) - |> deliverOnMainQueue).start(next: { settings in - updated(settings.filters) + |> deliverOnMainQueue).start(next: { _ in }) }) - let preferences = context.account.postbox.preferencesView(keys: [PreferencesKeys.chatListFilters, ApplicationSpecificPreferencesKeys.chatListFilterSettings]) + let chatCountCache = Atomic<[ChatListFilterData: Int]>(value: [:]) + + let filtersWithCountsSignal = updatedChatListFilters(postbox: context.account.postbox) + |> distinctUntilChanged + |> mapToSignal { filters -> Signal<[(ChatListFilter, Int)], NoError> in + return .single(filters.map { filter -> (ChatListFilter, Int) in + return (filter, 0) + }) + /*return context.account.postbox.transaction { transaction -> [(ChatListFilter, Int)] in + return filters.map { filter -> (ChatListFilter, Int) in + let count: Int + if let cachedValue = chatCountCache.with({ dict -> Int? in + return dict[filter.data] + }) { + count = cachedValue + } else { + count = transaction.getChatCountMatchingPredicate(chatListFilterPredicate(filter: filter.data)) + let _ = chatCountCache.modify { dict in + var dict = dict + dict[filter.data] = count + return dict + } + } + return (filter, count) + } + }*/ + } + + let featuredFilters = context.account.postbox.preferencesView(keys: [PreferencesKeys.chatListFiltersFeaturedState]) + |> map { preferences -> [ChatListFeaturedFilter] in + guard let state = preferences.values[PreferencesKeys.chatListFiltersFeaturedState] as? ChatListFiltersFeaturedState else { + return [] + } + return state.filters + } + |> distinctUntilChanged + + let filtersWithCounts = Promise<[(ChatListFilter, Int)]>() + filtersWithCounts.set(filtersWithCountsSignal) + + let updatedFilterOrder = Promise<[Int32]?>(nil) + + let preferences = context.account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.chatListFilterSettings]) let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, statePromise.get(), - preferences + filtersWithCounts.get(), + preferences, + updatedFilterOrder.get(), + featuredFilters ) - |> map { presentationData, state, preferences -> (ItemListControllerState, (ItemListNodeState, Any)) in - let filtersState = preferences.values[PreferencesKeys.chatListFilters] as? ChatListFiltersState ?? ChatListFiltersState.default + |> map { presentationData, state, filtersWithCountsValue, preferences, updatedFilterOrderValue, suggestedFilters -> (ItemListControllerState, (ItemListNodeState, Any)) in let filterSettings = preferences.values[ApplicationSpecificPreferencesKeys.chatListFilterSettings] as? ChatListFilterSettings ?? ChatListFilterSettings.default - let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Close), style: .regular, enabled: true, action: { - let _ = replaceRemoteChatListFilters(account: context.account).start() - dismissImpl?() - }) - let rightNavigationButton: ItemListNavigationButton + let leftNavigationButton: ItemListNavigationButton? + switch mode { + case .default: + leftNavigationButton = nil + case .modal: + leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Close), style: .regular, enabled: true, action: { + dismissImpl?() + }) + } + let rightNavigationButton: ItemListNavigationButton? if state.isEditing { - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { - updateState { state in - var state = state - state.isEditing = false - return state - } - }) - } else { + rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { + let _ = (updatedFilterOrder.get() + |> take(1) + |> deliverOnMainQueue).start(next: { [weak updatedFilterOrder] updatedFilterOrderValue in + if let updatedFilterOrderValue = updatedFilterOrderValue { + let _ = (updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in + var updatedFilters: [ChatListFilter] = [] + for id in updatedFilterOrderValue { + if let index = filters.firstIndex(where: { $0.id == id }) { + updatedFilters.append(filters[index]) + } + } + for filter in filters { + if !updatedFilters.contains(where: { $0.id == filter.id }) { + updatedFilters.append(filter) + } + } + + return updatedFilters + }) + |> deliverOnMainQueue).start(next: { _ in + filtersWithCounts.set(filtersWithCountsSignal) + let _ = (filtersWithCounts.get() + |> take(1) + |> deliverOnMainQueue).start(next: { _ in + updatedFilterOrder?.set(.single(nil)) + + updateState { state in + var state = state + state.isEditing = false + return state + } + }) + }) + } else { + updateState { state in + var state = state + state.isEditing = false + return state + } + } + }) + }) + } else if !filtersWithCountsValue.isEmpty { rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Edit), style: .regular, enabled: true, action: { updateState { state in var state = state @@ -205,10 +396,12 @@ func chatListFilterPresetListController(context: AccountContext, updated: @escap return state } }) + } else { + rightNavigationButton = nil } - let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text("Filters"), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: chatListFilterPresetListControllerEntries(presentationData: presentationData, state: state, filtersState: filtersState, settings: filterSettings), style: .blocks, animateChanges: true) + let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text("Folders"), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: chatListFilterPresetListControllerEntries(presentationData: presentationData, state: state, filters: filtersWithCountsValue, updatedFilterOrder: updatedFilterOrderValue, suggestedFilters: suggestedFilters, settings: filterSettings), style: .blocks, animateChanges: true) return (controllerState, (listState, arguments)) } @@ -216,13 +409,18 @@ func chatListFilterPresetListController(context: AccountContext, updated: @escap } let controller = ItemListController(context: context, state: signal) - controller.navigationPresentation = .modal + switch mode { + case .default: + controller.navigationPresentation = .default + case .modal: + controller.navigationPresentation = .modal + } + controller.didDisappear = { _ in + dismissed?() + } pushControllerImpl = { [weak controller] c in controller?.push(c) } - presentControllerImpl = { [weak controller] c, a in - controller?.present(c, in: .window(.root), with: a) - } dismissImpl = { [weak controller] in controller?.dismiss() } @@ -249,39 +447,48 @@ func chatListFilterPresetListController(context: AccountContext, updated: @escap afterAll = true } - return updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { filtersState in - var filtersState = filtersState - if let index = filtersState.filters.firstIndex(where: { $0.id == fromFilter.preset.id }) { - filtersState.filters.remove(at: index) + return combineLatest( + updatedFilterOrder.get() |> take(1), + filtersWithCounts.get() |> take(1) + ) + |> mapToSignal { updatedFilterOrderValue, filtersWithCountsValue -> Signal in + var filters = filtersWithAppliedOrder(filters: filtersWithCountsValue, order: updatedFilterOrderValue).map { $0.0 } + let initialOrder = filters.map { $0.id } + + if let index = filters.firstIndex(where: { $0.id == fromFilter.preset.id }) { + filters.remove(at: index) } if let referenceFilter = referenceFilter { var inserted = false - for i in 0 ..< filtersState.filters.count { - if filtersState.filters[i].id == referenceFilter.id { + for i in 0 ..< filters.count { + if filters[i].id == referenceFilter.id { if fromIndex < toIndex { - filtersState.filters.insert(fromFilter.preset, at: i + 1) + filters.insert(fromFilter.preset, at: i + 1) } else { - filtersState.filters.insert(fromFilter.preset, at: i) + filters.insert(fromFilter.preset, at: i) } inserted = true break } } if !inserted { - filtersState.filters.append(fromFilter.preset) + filters.append(fromFilter.preset) } } else if beforeAll { - filtersState.filters.insert(fromFilter.preset, at: 0) + filters.insert(fromFilter.preset, at: 0) } else if afterAll { - filtersState.filters.append(fromFilter.preset) + filters.append(fromFilter.preset) + } + + let updatedOrder = filters.map { $0.id } + if initialOrder != updatedOrder { + updatedFilterOrder.set(.single(updatedOrder)) + return .single(true) + } else { + return .single(false) } - return filtersState - }) - |> map { _ -> Bool in - return false } }) return controller } - diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift index be38d24b1d..77855286cc 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift @@ -20,6 +20,7 @@ final class ChatListFilterPresetListItem: ListViewItem, ItemListItem { let presentationData: ItemListPresentationData let preset: ChatListFilter let title: String + let label: String let editing: ChatListFilterPresetListItemEditing let canBeReordered: Bool let canBeDeleted: Bool @@ -32,6 +33,7 @@ final class ChatListFilterPresetListItem: ListViewItem, ItemListItem { presentationData: ItemListPresentationData, preset: ChatListFilter, title: String, + label: String, editing: ChatListFilterPresetListItemEditing, canBeReordered: Bool, canBeDeleted: Bool, @@ -43,6 +45,7 @@ final class ChatListFilterPresetListItem: ListViewItem, ItemListItem { self.presentationData = presentationData self.preset = preset self.title = title + self.label = label self.editing = editing self.canBeReordered = canBeReordered self.canBeDeleted = canBeDeleted @@ -108,6 +111,8 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN private let maskNode: ASImageNode private let titleNode: TextNode + private let labelNode: TextNode + private let arrowNode: ASImageNode private let activateArea: AccessibilityAreaNode @@ -141,6 +146,14 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN self.titleNode.contentMode = .left self.titleNode.contentsScale = UIScreen.main.scale + self.labelNode = TextNode() + self.labelNode.isUserInteractionEnabled = false + + self.arrowNode = ASImageNode() + self.arrowNode.displayWithoutProcessing = true + self.arrowNode.displaysAsynchronously = false + self.arrowNode.isLayerBacked = true + self.activateArea = AccessibilityAreaNode() self.highlightedBackgroundNode = ASDisplayNode() @@ -149,6 +162,8 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) self.addSubnode(self.titleNode) + self.addSubnode(self.labelNode) + self.addSubnode(self.arrowNode) self.addSubnode(self.activateArea) self.activateArea.activate = { [weak self] in @@ -159,6 +174,7 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN func asyncLayout() -> (_ item: ChatListFilterPresetListItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) { let makeTitleLayout = TextNode.asyncLayout(self.titleNode) + let makeLabelLayout = TextNode.asyncLayout(self.labelNode) let editableControlLayout = ItemListEditableControlNode.asyncLayout(self.editableControlNode) let reorderControlLayout = ItemListEditableReorderControlNode.asyncLayout(self.reorderControlNode) @@ -166,9 +182,11 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN return { item, params, neighbors in var updatedTheme: PresentationTheme? + var updateArrowImage: UIImage? if currentItem?.presentationData.theme !== item.presentationData.theme { updatedTheme = item.presentationData.theme + updateArrowImage = PresentationResourcesItemList.disclosureArrowImage(item.presentationData.theme) } let peerRevealOptions: [ItemListRevealOption] @@ -187,21 +205,27 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN var editingOffset: CGFloat = 0.0 var reorderInset: CGFloat = 0.0 - if item.editing.editing && item.canBeReordered { + if item.editing.editing { let sizeAndApply = editableControlLayout(item.presentationData.theme, false) editableControlSizeAndApply = sizeAndApply editingOffset = sizeAndApply.0 - let reorderSizeAndApply = reorderControlLayout(item.presentationData.theme) - reorderControlSizeAndApply = reorderSizeAndApply - reorderInset = reorderSizeAndApply.0 + if item.canBeReordered { + let reorderSizeAndApply = reorderControlLayout(item.presentationData.theme) + reorderControlSizeAndApply = reorderSizeAndApply + reorderInset = reorderSizeAndApply.0 + } } let leftInset: CGFloat = 16.0 + params.leftInset let rightInset: CGFloat = params.rightInset + max(reorderInset, 55.0) + let rightArrowInset: CGFloat = 34.0 + params.rightInset let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: params.width - leftInset - 12.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let labelConstrain: CGFloat = params.width - params.rightInset - leftInset - 40.0 - titleLayout.size.width - 10.0 + let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label, font: titleFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: labelConstrain, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let insets = itemListNeighborsGroupedInsets(neighbors) let contentSize = CGSize(width: params.width, height: titleLayout.size.height + 11.0 * 2.0) let separatorHeight = UIScreenPixel @@ -280,6 +304,7 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN } let _ = titleApply() + let _ = labelApply() if strongSelf.backgroundNode.supernode == nil { strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0) @@ -326,6 +351,20 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 11.0), size: titleLayout.size)) + let labelFrame = CGRect(origin: CGPoint(x: params.width - rightArrowInset - labelLayout.size.width + revealOffset, y: 11.0), size: labelLayout.size) + strongSelf.labelNode.frame = labelFrame + + transition.updateAlpha(node: strongSelf.labelNode, alpha: reorderControlSizeAndApply != nil ? 0.0 : 1.0) + transition.updateAlpha(node: strongSelf.arrowNode, alpha: reorderControlSizeAndApply != nil ? 0.0 : 1.0) + + if let updateArrowImage = updateArrowImage { + strongSelf.arrowNode.image = updateArrowImage + } + + if let arrowImage = strongSelf.arrowNode.image { + strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 7.0 - arrowImage.size.width + revealOffset, y: floorToScreenPixels((layout.contentSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size) + } + strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 0.0), size: CGSize(width: params.width - params.rightInset - 56.0 - (leftInset + revealOffset + editingOffset), height: layout.contentSize.height)) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel)) @@ -393,6 +432,7 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN } let leftInset: CGFloat = 16.0 + params.leftInset + let rightArrowInset: CGFloat = 34.0 + params.rightInset let editingOffset: CGFloat if let editableControlNode = self.editableControlNode { @@ -405,6 +445,14 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN } transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + offset + editingOffset, y: self.titleNode.frame.minY), size: self.titleNode.bounds.size)) + + var labelFrame = self.labelNode.frame + labelFrame.origin.x = params.width - rightArrowInset - labelFrame.width + revealOffset + transition.updateFrame(node: self.labelNode, frame: labelFrame) + + var arrowFrame = self.arrowNode.frame + arrowFrame.origin.x = params.width - params.rightInset - 7.0 - arrowFrame.width + revealOffset + transition.updateFrame(node: self.arrowNode, frame: arrowFrame) } override func revealOptionsInteractivelyOpened() { diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListSuggestedItem.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListSuggestedItem.swift new file mode 100644 index 0000000000..225d95d2b1 --- /dev/null +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListSuggestedItem.swift @@ -0,0 +1,381 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit +import TelegramPresentationData +import ItemListUI + +public class ChatListFilterPresetListSuggestedItem: ListViewItem, ItemListItem { + let presentationData: ItemListPresentationData + let title: String + let label: String + public let sectionId: ItemListSectionId + let style: ItemListStyle + let installAction: (() -> Void)? + public let tag: ItemListItemTag? + + public init( + presentationData: ItemListPresentationData, + title: String, + label: String, + sectionId: ItemListSectionId, + style: ItemListStyle, + installAction: (() -> Void)?, + tag: ItemListItemTag? = nil + ) { + self.presentationData = presentationData + self.title = title + self.label = label + self.sectionId = sectionId + self.style = style + self.installAction = installAction + self.tag = tag + } + + public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { + async { + let node = ChatListFilterPresetListSuggestedItemNode() + let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) + + node.contentSize = layout.contentSize + node.insets = layout.insets + + Queue.mainQueue().async { + completion(node, { + return (nil, { _ in apply() }) + }) + } + } + } + + public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { + Queue.mainQueue().async { + if let nodeValue = node() as? ChatListFilterPresetListSuggestedItemNode { + let makeLayout = nodeValue.asyncLayout() + + async { + let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) + Queue.mainQueue().async { + completion(layout, { _ in + apply() + }) + } + } + } + } + } + + public var selectable: Bool = false + + public func selected(listView: ListView){ + } +} + +public class ChatListFilterPresetListSuggestedItemNode: ListViewItemNode, ItemListItemNode { + private let backgroundNode: ASDisplayNode + private let topStripeNode: ASDisplayNode + private let bottomStripeNode: ASDisplayNode + private let highlightedBackgroundNode: ASDisplayNode + private let maskNode: ASImageNode + + private let titleNode: TextNode + private let labelNode: TextNode + private let buttonBackgroundNode: ASImageNode + private let buttonTitleNode: TextNode + private let buttonNode: HighlightTrackingButtonNode + + private let activateArea: AccessibilityAreaNode + + private var item: ChatListFilterPresetListSuggestedItem? + + override public var canBeSelected: Bool { + return false + } + + public var tag: ItemListItemTag? { + return self.item?.tag + } + + public init() { + self.backgroundNode = ASDisplayNode() + self.backgroundNode.isLayerBacked = true + self.backgroundNode.backgroundColor = .white + + self.maskNode = ASImageNode() + + self.topStripeNode = ASDisplayNode() + self.topStripeNode.isLayerBacked = true + + self.bottomStripeNode = ASDisplayNode() + self.bottomStripeNode.isLayerBacked = true + + self.titleNode = TextNode() + self.titleNode.isUserInteractionEnabled = false + + self.labelNode = TextNode() + self.labelNode.isUserInteractionEnabled = false + + self.buttonBackgroundNode = ASImageNode() + self.buttonBackgroundNode.isUserInteractionEnabled = false + + self.buttonTitleNode = TextNode() + self.buttonTitleNode.isUserInteractionEnabled = false + + self.highlightedBackgroundNode = ASDisplayNode() + self.highlightedBackgroundNode.isLayerBacked = true + + self.buttonNode = HighlightTrackingButtonNode() + + self.activateArea = AccessibilityAreaNode() + + super.init(layerBacked: false, dynamicBounce: false) + + self.addSubnode(self.titleNode) + self.addSubnode(self.labelNode) + + self.addSubnode(self.buttonBackgroundNode) + self.addSubnode(self.buttonTitleNode) + self.addSubnode(self.buttonNode) + + self.addSubnode(self.activateArea) + + self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + self.buttonNode.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.buttonBackgroundNode.layer.removeAnimation(forKey: "opacity") + strongSelf.buttonBackgroundNode.alpha = 0.7 + } else { + strongSelf.buttonBackgroundNode.alpha = 1.0 + strongSelf.buttonBackgroundNode.layer.animateAlpha(from: 0.7, to: 1.0, duration: 0.3) + } + } + } + } + + @objc private func buttonPressed() { + self.item?.installAction?() + } + + public func asyncLayout() -> (_ item: ChatListFilterPresetListSuggestedItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { + let makeTitleLayout = TextNode.asyncLayout(self.titleNode) + let makeLabelLayout = TextNode.asyncLayout(self.labelNode) + let makeButtonTitleLayout = TextNode.asyncLayout(self.buttonTitleNode) + + let currentItem = self.item + + return { item, params, neighbors in + let rightInset: CGFloat + rightInset = 16.0 + params.rightInset + + var updatedTheme: PresentationTheme? + var updatedButtonImage: UIImage? + let buttonDiameter: CGFloat = 28.0 + if currentItem?.presentationData.theme !== item.presentationData.theme { + updatedTheme = item.presentationData.theme + updatedButtonImage = generateStretchableFilledCircleImage(diameter: buttonDiameter, color: item.presentationData.theme.list.itemCheckColors.fillColor) + } + + let contentSize: CGSize + let insets: UIEdgeInsets + let separatorHeight = UIScreenPixel + let itemBackgroundColor: UIColor + let itemSeparatorColor: UIColor + + let leftInset = 16.0 + params.leftInset + + let titleColor: UIColor + titleColor = item.presentationData.theme.list.itemPrimaryTextColor + + let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize) + + let (buttonTitleLayout, buttonTitleApply) = makeButtonTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Stickers_Install, font: Font.semibold(14.0), textColor: item.presentationData.theme.list.itemCheckColors.foregroundColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let additionalTextRightInset: CGFloat = buttonTitleLayout.size.width + 16.0 + + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - leftInset - rightInset - additionalTextRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let detailFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 13.0 / 17.0)) + + let labelFont: UIFont + let labelBadgeColor: UIColor + var labelConstrain: CGFloat = params.width - params.rightInset - leftInset - 40.0 - titleLayout.size.width - 10.0 + + labelBadgeColor = item.presentationData.theme.list.itemSecondaryTextColor + labelFont = detailFont + labelConstrain = params.width - params.rightInset - 40.0 - leftInset + + let multilineLabel = false + + let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label, font: labelFont, textColor:labelBadgeColor), backgroundColor: nil, maximumNumberOfLines: multilineLabel ? 0 : 1, truncationType: .end, constrainedSize: CGSize(width: labelConstrain, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let verticalInset: CGFloat = 11.0 + let titleSpacing: CGFloat = 3.0 + + let height: CGFloat + height = verticalInset * 2.0 + titleLayout.size.height + titleSpacing + labelLayout.size.height + + switch item.style { + case .plain: + itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor + itemSeparatorColor = item.presentationData.theme.list.itemPlainSeparatorColor + contentSize = CGSize(width: params.width, height: height) + insets = itemListNeighborsPlainInsets(neighbors) + case .blocks: + itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor + itemSeparatorColor = item.presentationData.theme.list.itemBlocksSeparatorColor + contentSize = CGSize(width: params.width, height: height) + insets = itemListNeighborsGroupedInsets(neighbors) + } + + let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) + + return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] in + if let strongSelf = self { + strongSelf.item = item + + strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height)) + strongSelf.activateArea.accessibilityLabel = item.title + strongSelf.activateArea.accessibilityValue = item.label + strongSelf.activateArea.accessibilityTraits = [] + + if let _ = updatedTheme { + strongSelf.topStripeNode.backgroundColor = itemSeparatorColor + strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor + strongSelf.backgroundNode.backgroundColor = itemBackgroundColor + strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor + } + + let _ = titleApply() + let _ = labelApply() + let _ = buttonTitleApply() + + switch item.style { + case .plain: + if strongSelf.backgroundNode.supernode != nil { + strongSelf.backgroundNode.removeFromSupernode() + } + if strongSelf.topStripeNode.supernode != nil { + strongSelf.topStripeNode.removeFromSupernode() + } + if strongSelf.bottomStripeNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0) + } + if strongSelf.maskNode.supernode != nil { + strongSelf.maskNode.removeFromSupernode() + } + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight)) + case .blocks: + if strongSelf.backgroundNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0) + } + if strongSelf.topStripeNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1) + } + if strongSelf.bottomStripeNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2) + } + if strongSelf.maskNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.maskNode, at: 3) + } + + let hasCorners = itemListHasRoundedBlockLayout(params) + var hasTopCorners = false + var hasBottomCorners = false + switch neighbors.top { + case .sameSection(false): + strongSelf.topStripeNode.isHidden = true + default: + hasTopCorners = true + strongSelf.topStripeNode.isHidden = hasCorners + } + let bottomStripeInset: CGFloat + switch neighbors.bottom { + case .sameSection(false): + bottomStripeInset = leftInset + default: + bottomStripeInset = 0.0 + hasBottomCorners = true + strongSelf.bottomStripeNode.isHidden = hasCorners + } + + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + + strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) + strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) + strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) + } + + let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: 11.0), size: titleLayout.size) + strongSelf.titleNode.frame = titleFrame + + let labelFrame = CGRect(origin: CGPoint(x: leftInset, y: titleFrame.maxY + titleSpacing), size: labelLayout.size) + strongSelf.labelNode.frame = labelFrame + + let buttonSize = CGSize(width: buttonTitleLayout.size.width + 14.0 * 2.0, height: buttonDiameter) + let buttonFrame = CGRect(origin: CGPoint(x: params.width - rightInset - buttonSize.width, y: floor((layout.contentSize.height - buttonSize.height) / 2.0)), size: buttonSize) + strongSelf.buttonNode.frame = buttonFrame + if let updatedButtonImage = updatedButtonImage { + strongSelf.buttonBackgroundNode.image = updatedButtonImage + } + strongSelf.buttonBackgroundNode.frame = buttonFrame + strongSelf.buttonTitleNode.frame = CGRect(origin: CGPoint(x: buttonFrame.minX + floor((buttonFrame.width - buttonTitleLayout.size.width) / 2.0), y: buttonFrame.minY + 1.0 + floor((buttonFrame.height - buttonTitleLayout.size.height) / 2.0)), size: buttonTitleLayout.size) + + strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: height + UIScreenPixel)) + } + }) + } + } + + override public func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { + super.setHighlighted(highlighted, at: point, animated: animated) + + if highlighted && false { + self.highlightedBackgroundNode.alpha = 1.0 + if self.highlightedBackgroundNode.supernode == nil { + var anchorNode: ASDisplayNode? + if self.bottomStripeNode.supernode != nil { + anchorNode = self.bottomStripeNode + } else if self.topStripeNode.supernode != nil { + anchorNode = self.topStripeNode + } else if self.backgroundNode.supernode != nil { + anchorNode = self.backgroundNode + } + if let anchorNode = anchorNode { + self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode) + } else { + self.addSubnode(self.highlightedBackgroundNode) + } + } + } else { + if self.highlightedBackgroundNode.supernode != nil { + if animated { + self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in + if let strongSelf = self { + if completed { + strongSelf.highlightedBackgroundNode.removeFromSupernode() + } + } + }) + self.highlightedBackgroundNode.alpha = 0.0 + } else { + self.highlightedBackgroundNode.removeFromSupernode() + } + } + } + } + + override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) + } + + override public func animateAdded(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) + } +} diff --git a/submodules/ChatListUI/Sources/ChatListFilterSettingsHeaderItem.swift b/submodules/ChatListUI/Sources/ChatListFilterSettingsHeaderItem.swift new file mode 100644 index 0000000000..006a39ce43 --- /dev/null +++ b/submodules/ChatListUI/Sources/ChatListFilterSettingsHeaderItem.swift @@ -0,0 +1,153 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit +import TelegramPresentationData +import ItemListUI +import PresentationDataUtils +import AnimatedStickerNode +import AppBundle + +enum ChatListFilterSettingsHeaderAnimation { + case folders + case newFolder +} + +class ChatListFilterSettingsHeaderItem: ListViewItem, ItemListItem { + let theme: PresentationTheme + let text: String + let animation: ChatListFilterSettingsHeaderAnimation + let sectionId: ItemListSectionId + + init(theme: PresentationTheme, text: String, animation: ChatListFilterSettingsHeaderAnimation, sectionId: ItemListSectionId) { + self.theme = theme + self.text = text + self.animation = animation + self.sectionId = sectionId + } + + func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { + async { + let node = ChatListFilterSettingsHeaderItemNode() + let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) + + node.contentSize = layout.contentSize + node.insets = layout.insets + + Queue.mainQueue().async { + completion(node, { + return (nil, { _ in apply() }) + }) + } + } + } + + func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { + Queue.mainQueue().async { + guard let nodeValue = node() as? ChatListFilterSettingsHeaderItemNode else { + assertionFailure() + return + } + + let makeLayout = nodeValue.asyncLayout() + + async { + let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) + Queue.mainQueue().async { + completion(layout, { _ in + apply() + }) + } + } + } + } +} + +private let titleFont = Font.regular(13.0) + +class ChatListFilterSettingsHeaderItemNode: ListViewItemNode { + private let titleNode: TextNode + private var animationNode: AnimatedStickerNode + + private var item: ChatListFilterSettingsHeaderItem? + + init() { + self.titleNode = TextNode() + self.titleNode.isUserInteractionEnabled = false + self.titleNode.contentMode = .left + self.titleNode.contentsScale = UIScreen.main.scale + + self.animationNode = AnimatedStickerNode() + + super.init(layerBacked: false, dynamicBounce: false) + + self.addSubnode(self.titleNode) + self.addSubnode(self.animationNode) + } + + override func didLoad() { + super.didLoad() + + self.animationNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.animationTapGesture(_:)))) + } + + @objc private func animationTapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.animationNode.play() + } + } + + func asyncLayout() -> (_ item: ChatListFilterSettingsHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { + let makeTitleLayout = TextNode.asyncLayout(self.titleNode) + + return { item, params, neighbors in + let leftInset: CGFloat = 32.0 + params.leftInset + let topInset: CGFloat = 92.0 + + let attributedText = NSAttributedString(string: item.text, font: titleFont, textColor: item.theme.list.freeTextColor) + 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: .center, cutout: nil, insets: UIEdgeInsets())) + + let contentSize = CGSize(width: params.width, height: topInset + titleLayout.size.height) + let insets = itemListNeighborsGroupedInsets(neighbors) + + let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) + + return (layout, { [weak self] in + if let strongSelf = self { + if strongSelf.item == nil { + let animationName: String + switch item.animation { + case .folders: + animationName = "ChatListFolders" + case .newFolder: + animationName = "ChatListNewFolder" + } + if let path = getAppBundle().path(forResource: animationName, ofType: "tgs") { + strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 192, height: 192, playbackMode: .once, mode: .direct) + strongSelf.animationNode.visibility = true + } + } + + strongSelf.item = item + strongSelf.accessibilityLabel = attributedText.string + + let iconSize = CGSize(width: 96.0, height: 96.0) + strongSelf.animationNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - iconSize.width) / 2.0), y: -10.0), size: iconSize) + strongSelf.animationNode.updateLayout(size: iconSize) + + let _ = titleApply() + strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleLayout.size.width) / 2.0), y: topInset + 8.0), size: titleLayout.size) + } + }) + } + } + + override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) + } + + override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) + } +} diff --git a/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift b/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift index 4d8a690927..87f91df2d3 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift @@ -7,36 +7,94 @@ import Postbox import TelegramCore import TelegramPresentationData +private final class ItemNodeDeleteButtonNode: HighlightableButtonNode { + private let pressed: () -> Void + + private let contentImageNode: ASImageNode + + private var theme: PresentationTheme? + + init(pressed: @escaping () -> Void) { + self.pressed = pressed + + self.contentImageNode = ASImageNode() + + super.init() + + self.addSubnode(self.contentImageNode) + + self.addTarget(self, action: #selector(self.pressedEvent), forControlEvents: .touchUpInside) + } + + @objc private func pressedEvent() { + self.pressed() + } + + func update(theme: PresentationTheme) -> CGSize { + let size = CGSize(width: 18.0, height: 18.0) + if self.theme !== theme { + self.theme = theme + self.contentImageNode.image = generateImage(size, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(theme.rootController.navigationBar.clearButtonBackgroundColor.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(theme.rootController.navigationBar.clearButtonForegroundColor.cgColor) + context.setLineWidth(1.5) + context.setLineCap(.round) + context.move(to: CGPoint(x: 6.38, y: 6.38)) + context.addLine(to: CGPoint(x: 11.63, y: 11.63)) + context.strokePath() + context.move(to: CGPoint(x: 6.38, y: 11.63)) + context.addLine(to: CGPoint(x: 11.63, y: 6.38)) + context.strokePath() + }) + } + + self.contentImageNode.frame = CGRect(origin: CGPoint(), size: size) + + return size + } +} + private final class ItemNode: ASDisplayNode { private let pressed: () -> Void + private let requestedDeletion: () -> Void private let extractedContainerNode: ContextExtractedContentContainingNode private let containerNode: ContextControllerSourceNode + private let extractedBackgroundNode: ASImageNode private let titleNode: ImmediateTextNode - private let extractedTitleNode: ImmediateTextNode + private let shortTitleNode: ImmediateTextNode private let badgeContainerNode: ASDisplayNode private let badgeTextNode: ImmediateTextNode private let badgeBackgroundNode: ASImageNode + private var deleteButtonNode: ItemNodeDeleteButtonNode? private let buttonNode: HighlightTrackingButtonNode private var isSelected: Bool = false private(set) var unreadCount: Int = 0 + private var isReordering: Bool = false + private var theme: PresentationTheme? - init(pressed: @escaping () -> Void, contextGesture: @escaping (ContextExtractedContentContainingNode, ContextGesture) -> Void) { + init(pressed: @escaping () -> Void, requestedDeletion: @escaping () -> Void, contextGesture: @escaping (ContextExtractedContentContainingNode, ContextGesture) -> Void) { self.pressed = pressed + self.requestedDeletion = requestedDeletion self.extractedContainerNode = ContextExtractedContentContainingNode() self.containerNode = ContextControllerSourceNode() + self.extractedBackgroundNode = ASImageNode() + self.extractedBackgroundNode.alpha = 0.0 + self.titleNode = ImmediateTextNode() self.titleNode.displaysAsynchronously = false - self.extractedTitleNode = ImmediateTextNode() - self.extractedTitleNode.displaysAsynchronously = false - self.extractedTitleNode.alpha = 0.0 + self.shortTitleNode = ImmediateTextNode() + self.shortTitleNode.displaysAsynchronously = false + self.shortTitleNode.alpha = 0.0 self.badgeContainerNode = ASDisplayNode() @@ -51,8 +109,9 @@ private final class ItemNode: ASDisplayNode { super.init() + self.extractedContainerNode.contentNode.addSubnode(self.extractedBackgroundNode) self.extractedContainerNode.contentNode.addSubnode(self.titleNode) - self.extractedContainerNode.contentNode.addSubnode(self.extractedTitleNode) + self.extractedContainerNode.contentNode.addSubnode(self.shortTitleNode) self.badgeContainerNode.addSubnode(self.badgeBackgroundNode) self.badgeContainerNode.addSubnode(self.badgeTextNode) self.extractedContainerNode.contentNode.addSubnode(self.badgeContainerNode) @@ -75,8 +134,15 @@ private final class ItemNode: ASDisplayNode { guard let strongSelf = self else { return } - transition.updateAlpha(node: strongSelf.titleNode, alpha: isExtracted ? 0.0 : 1.0) - transition.updateAlpha(node: strongSelf.extractedTitleNode, alpha: isExtracted ? 1.0 : 0.0) + + if isExtracted, let theme = strongSelf.theme { + strongSelf.extractedBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 28.0, color: theme.contextMenu.backgroundColor) + } + transition.updateAlpha(node: strongSelf.extractedBackgroundNode, alpha: isExtracted ? 1.0 : 0.0, completion: { _ in + if !isExtracted { + self?.extractedBackgroundNode.image = nil + } + }) } } @@ -84,30 +150,73 @@ private final class ItemNode: ASDisplayNode { self.pressed() } - func updateText(title: String, unreadCount: Int, isNoFilter: Bool, isSelected: Bool, presentationData: PresentationData) { + func updateText(title: String, shortTitle: String, unreadCount: Int, isNoFilter: Bool, isSelected: Bool, isEditing: Bool, isAllChats: Bool, isReordering: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) { if self.theme !== presentationData.theme { self.theme = presentationData.theme self.badgeBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: presentationData.theme.list.itemCheckColors.fillColor) } - self.containerNode.isGestureEnabled = !isNoFilter + self.containerNode.isGestureEnabled = !isNoFilter && !isEditing && !isReordering + self.buttonNode.isUserInteractionEnabled = !isEditing && !isReordering self.isSelected = isSelected self.unreadCount = unreadCount + transition.updateAlpha(node: self.containerNode, alpha: isReordering && isAllChats ? 0.5 : 1.0) + + if isReordering && !isAllChats { + if self.deleteButtonNode == nil { + let deleteButtonNode = ItemNodeDeleteButtonNode(pressed: { [weak self] in + self?.requestedDeletion() + }) + self.extractedContainerNode.contentNode.addSubnode(deleteButtonNode) + self.deleteButtonNode = deleteButtonNode + if case .animated = transition { + deleteButtonNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.25) + deleteButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + } + } + } else if let deleteButtonNode = self.deleteButtonNode { + self.deleteButtonNode = nil + transition.updateTransformScale(node: deleteButtonNode, scale: 0.1) + transition.updateAlpha(node: deleteButtonNode, alpha: 0.0, completion: { [weak deleteButtonNode] _ in + deleteButtonNode?.removeFromSupernode() + }) + } + + transition.updateAlpha(node: self.badgeContainerNode, alpha: (isReordering || unreadCount == 0) ? 0.0 : 1.0) + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(14.0), textColor: isSelected ? presentationData.theme.list.itemAccentColor : presentationData.theme.list.itemSecondaryTextColor) - self.extractedTitleNode.attributedText = NSAttributedString(string: title, font: Font.medium(14.0), textColor: presentationData.theme.contextMenu.extractedContentTintColor) + self.shortTitleNode.attributedText = NSAttributedString(string: shortTitle, font: Font.medium(14.0), textColor: isSelected ? presentationData.theme.list.itemAccentColor : presentationData.theme.list.itemSecondaryTextColor) if unreadCount != 0 { self.badgeTextNode.attributedText = NSAttributedString(string: "\(unreadCount)", font: Font.regular(14.0), textColor: presentationData.theme.list.itemCheckColors.foregroundColor) } + + if self.isReordering != isReordering { + self.isReordering = isReordering + if self.isReordering && !isAllChats { + self.startShaking() + } else { + self.layer.removeAnimation(forKey: "shaking_position") + self.layer.removeAnimation(forKey: "shaking_rotation") + } + } } - func updateLayout(height: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { - let titleSize = self.titleNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude)) - let _ = self.extractedTitleNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude)) + func updateLayout(height: CGFloat, transition: ContainedViewLayoutTransition) -> (width: CGFloat, shortWidth: CGFloat) { + let titleSize = self.titleNode.updateLayout(CGSize(width: 160.0, height: .greatestFiniteMagnitude)) self.titleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: floor((height - titleSize.height) / 2.0)), size: titleSize) - self.extractedTitleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: floor((height - titleSize.height) / 2.0)), size: titleSize) + + let shortTitleSize = self.shortTitleNode.updateLayout(CGSize(width: 160.0, height: .greatestFiniteMagnitude)) + self.shortTitleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: floor((height - shortTitleSize.height) / 2.0)), size: shortTitleSize) + + if let deleteButtonNode = self.deleteButtonNode { + if let theme = self.theme { + let deleteButtonSize = deleteButtonNode.update(theme: theme) + deleteButtonNode.frame = CGRect(origin: CGPoint(x: -deleteButtonSize.width, y: 5.0), size: deleteButtonSize) + } + } let badgeSize = self.badgeTextNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude)) let badgeInset: CGFloat = 4.0 @@ -116,21 +225,35 @@ private final class ItemNode: ASDisplayNode { self.badgeBackgroundNode.frame = CGRect(origin: CGPoint(), size: badgeBackgroundFrame.size) self.badgeTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((badgeBackgroundFrame.width - badgeSize.width) / 2.0), y: floor((badgeBackgroundFrame.height - badgeSize.height) / 2.0)), size: badgeSize) - if self.unreadCount == 0 { - self.badgeContainerNode.alpha = 0.0 - return titleSize.width + let width: CGFloat + if self.unreadCount == 0 || self.isReordering { + if !self.isReordering { + self.badgeContainerNode.alpha = 0.0 + } + width = titleSize.width } else { - self.badgeContainerNode.alpha = 1.0 - return badgeBackgroundFrame.maxX + if !self.isReordering { + self.badgeContainerNode.alpha = 1.0 + } + width = badgeBackgroundFrame.maxX } + + let extractedBackgroundHeight: CGFloat = 36.0 + let extractedBackgroundInset: CGFloat = 14.0 + self.extractedBackgroundNode.frame = CGRect(origin: CGPoint(x: -extractedBackgroundInset, y: floor((height - extractedBackgroundHeight) / 2.0)), size: CGSize(width: width + extractedBackgroundInset * 2.0, height: extractedBackgroundHeight)) + + return (width, shortTitleSize.width) } - func updateArea(size: CGSize, sideInset: CGFloat) { + func updateArea(size: CGSize, sideInset: CGFloat, useShortTitle: Bool, transition: ContainedViewLayoutTransition) { + transition.updateAlpha(node: self.titleNode, alpha: useShortTitle ? 0.0 : 1.0) + transition.updateAlpha(node: self.shortTitleNode, alpha: useShortTitle ? 1.0 : 0.0) + self.buttonNode.frame = CGRect(origin: CGPoint(x: -sideInset, y: 0.0), size: CGSize(width: size.width + sideInset * 2.0, height: size.height)) self.extractedContainerNode.frame = CGRect(origin: CGPoint(), size: size) self.extractedContainerNode.contentNode.frame = CGRect(origin: CGPoint(), size: size) - self.extractedContainerNode.contentRect = CGRect(origin: CGPoint(), size: size) + self.extractedContainerNode.contentRect = CGRect(origin: CGPoint(x: self.extractedBackgroundNode.frame.minX, y: 0.0), size: CGSize(width:self.extractedBackgroundNode.frame.width, height: size.height)) self.containerNode.frame = CGRect(origin: CGPoint(), size: size) self.hitTestSlop = UIEdgeInsets(top: 0.0, left: -sideInset, bottom: 0.0, right: -sideInset) @@ -140,17 +263,75 @@ private final class ItemNode: ASDisplayNode { } func animateBadgeIn() { - let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) - self.badgeContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) - ContainedViewLayoutTransition.immediate.updateSublayerTransformScale(node: self.badgeContainerNode, scale: 0.1) - transition.updateSublayerTransformScale(node: self.badgeContainerNode, scale: 1.0) + if !self.isReordering { + let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) + self.badgeContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + ContainedViewLayoutTransition.immediate.updateSublayerTransformScale(node: self.badgeContainerNode, scale: 0.1) + transition.updateSublayerTransformScale(node: self.badgeContainerNode, scale: 1.0) + } } func animateBadgeOut() { - let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) - self.badgeContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25) - ContainedViewLayoutTransition.immediate.updateSublayerTransformScale(node: self.badgeContainerNode, scale: 1.0) - transition.updateSublayerTransformScale(node: self.badgeContainerNode, scale: 0.1) + if !self.isReordering { + let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) + self.badgeContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25) + ContainedViewLayoutTransition.immediate.updateSublayerTransformScale(node: self.badgeContainerNode, scale: 1.0) + transition.updateSublayerTransformScale(node: self.badgeContainerNode, scale: 0.1) + } + } + + private func startShaking() { + func degreesToRadians(_ x: CGFloat) -> CGFloat { + return .pi * x / 180.0 + } + + let duration: Double = 0.4 + let displacement: CGFloat = 1.0 + let degreesRotation: CGFloat = 2.0 + + let negativeDisplacement = -1.0 * displacement + let position = CAKeyframeAnimation.init(keyPath: "position") + position.beginTime = 0.8 + position.duration = duration + position.values = [ + NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: negativeDisplacement)), + NSValue(cgPoint: CGPoint(x: 0, y: 0)), + NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: 0)), + NSValue(cgPoint: CGPoint(x: 0, y: negativeDisplacement)), + NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: negativeDisplacement)) + ] + position.calculationMode = .linear + position.isRemovedOnCompletion = false + position.repeatCount = Float.greatestFiniteMagnitude + position.beginTime = CFTimeInterval(Float(arc4random()).truncatingRemainder(dividingBy: Float(25)) / Float(100)) + position.isAdditive = true + + let transform = CAKeyframeAnimation.init(keyPath: "transform") + transform.beginTime = 2.6 + transform.duration = 0.3 + transform.valueFunction = CAValueFunction(name: CAValueFunctionName.rotateZ) + transform.values = [ + degreesToRadians(-1.0 * degreesRotation), + degreesToRadians(degreesRotation), + degreesToRadians(-1.0 * degreesRotation) + ] + transform.calculationMode = .linear + transform.isRemovedOnCompletion = false + transform.repeatCount = Float.greatestFiniteMagnitude + transform.isAdditive = true + transform.beginTime = CFTimeInterval(Float(arc4random()).truncatingRemainder(dividingBy: Float(25)) / Float(100)) + + self.layer.add(position, forKey: "shaking_position") + self.layer.add(transform, forKey: "shaking_rotation") + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let deleteButtonNode = self.deleteButtonNode { + if deleteButtonNode.frame.insetBy(dx: -4.0, dy: -4.0).contains(point) { + return deleteButtonNode.view + } + } + return super.hitTest(point, with: event) } } @@ -180,39 +361,13 @@ enum ChatListFilterTabEntry: Equatable { return filter.text } } -} - -private final class AddItemNode: HighlightableButtonNode { - private let iconNode: ASImageNode - var pressed: (() -> Void)? - - private var theme: PresentationTheme? - - override init() { - self.iconNode = ASImageNode() - self.iconNode.displaysAsynchronously = false - self.iconNode.displayWithoutProcessing = true - - super.init() - - self.addSubnode(self.iconNode) - - self.addTarget(self, action: #selector(self.onPressed), forControlEvents: .touchUpInside) - } - - @objc private func onPressed() { - self.pressed?() - } - - func update(size: CGSize, theme: PresentationTheme) { - if self.theme !== theme { - self.theme = theme - self.iconNode.image = PresentationResourcesItemList.plusIconImage(theme) - } - - if let image = self.iconNode.image { - self.iconNode.frame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size) + func shortTitle(strings: PresentationStrings) -> String { + switch self { + case .all: + return "All" + case let .filter(filter): + return filter.text } } } @@ -221,13 +376,33 @@ final class ChatListFilterTabContainerNode: ASDisplayNode { private let scrollNode: ASScrollNode private let selectedLineNode: ASImageNode private var itemNodes: [ChatListFilterTabEntryId: ItemNode] = [:] - private let addNode: AddItemNode var tabSelected: ((ChatListFilterTabEntryId) -> Void)? + var tabRequestedDeletion: ((ChatListFilterTabEntryId) -> Void)? var addFilter: (() -> Void)? var contextGesture: ((Int32, ContextExtractedContentContainingNode, ContextGesture) -> Void)? - private var currentParams: (size: CGSize, sideInset: CGFloat, filters: [ChatListFilterTabEntry], selectedFilter: ChatListFilterTabEntryId?, presentationData: PresentationData)? + private var reorderingGesture: ReorderingGestureRecognizer? + private var reorderingItem: ChatListFilterTabEntryId? + private var reorderingItemPosition: (initial: CGFloat, offset: CGFloat)? + private var reorderingAutoScrollAnimator: ConstantDisplayLinkAnimator? + private var reorderedItemIds: [ChatListFilterTabEntryId]? + private lazy var hapticFeedback = { HapticFeedback() }() + + private var currentParams: (size: CGSize, sideInset: CGFloat, filters: [ChatListFilterTabEntry], selectedFilter: ChatListFilterTabEntryId?, isReordering: Bool, isEditing: Bool, transitionFraction: CGFloat, presentationData: PresentationData)? + + var reorderedFilterIds: [Int32]? { + return self.reorderedItemIds.flatMap { + $0.compactMap { + switch $0 { + case .all: + return nil + case let .filter(id): + return id + } + } + } + } override init() { self.scrollNode = ASScrollNode() @@ -236,8 +411,6 @@ final class ChatListFilterTabContainerNode: ASDisplayNode { self.selectedLineNode.displaysAsynchronously = false self.selectedLineNode.displayWithoutProcessing = true - self.addNode = AddItemNode() - super.init() self.scrollNode.view.showsHorizontalScrollIndicator = false @@ -250,20 +423,136 @@ final class ChatListFilterTabContainerNode: ASDisplayNode { self.addSubnode(self.scrollNode) self.scrollNode.addSubnode(self.selectedLineNode) - self.scrollNode.addSubnode(self.addNode) - self.addNode.pressed = { [weak self] in - self?.addFilter?() - } + let reorderingGesture = ReorderingGestureRecognizer(shouldBegin: { [weak self] point in + guard let strongSelf = self else { + return false + } + for (id, itemNode) in strongSelf.itemNodes { + if itemNode.view.convert(itemNode.bounds, to: strongSelf.view).contains(point) { + if case .all = id { + return false + } + return true + } + } + return false + }, began: { [weak self] point in + guard let strongSelf = self, let _ = strongSelf.currentParams else { + return + } + for (id, itemNode) in strongSelf.itemNodes { + let itemFrame = itemNode.view.convert(itemNode.bounds, to: strongSelf.view) + if itemFrame.contains(point) { + strongSelf.hapticFeedback.impact() + + strongSelf.reorderingItem = id + itemNode.frame = itemFrame + strongSelf.reorderingAutoScrollAnimator = ConstantDisplayLinkAnimator(update: { + guard let strongSelf = self, let currentLocation = strongSelf.reorderingGesture?.currentLocation else { + return + } + let edgeWidth: CGFloat = 20.0 + if currentLocation.x <= edgeWidth { + var contentOffset = strongSelf.scrollNode.view.contentOffset + contentOffset.x = max(0.0, contentOffset.x - 3.0) + strongSelf.scrollNode.view.setContentOffset(contentOffset, animated: false) + } else if currentLocation.x >= strongSelf.bounds.width - edgeWidth { + var contentOffset = strongSelf.scrollNode.view.contentOffset + contentOffset.x = max(0.0, min(strongSelf.scrollNode.view.contentSize.width - strongSelf.scrollNode.bounds.width, contentOffset.x + 3.0)) + strongSelf.scrollNode.view.setContentOffset(contentOffset, animated: false) + } + }) + strongSelf.reorderingAutoScrollAnimator?.isPaused = false + strongSelf.addSubnode(itemNode) + + strongSelf.reorderingItemPosition = (itemNode.frame.minX, 0.0) + if let (size, sideInset, filters, selectedFilter, isReordering, isEditing, transitionFraction, presentationData) = strongSelf.currentParams { + strongSelf.update(size: size, sideInset: sideInset, filters: filters, selectedFilter: selectedFilter, isReordering: isReordering, isEditing: isEditing, transitionFraction: transitionFraction, presentationData: presentationData, transition: .animated(duration: 0.25, curve: .easeInOut)) + } + return + } + } + }, ended: { [weak self] in + guard let strongSelf = self, let reorderingItem = strongSelf.reorderingItem else { + return + } + if let itemNode = strongSelf.itemNodes[reorderingItem] { + let projectedItemFrame = itemNode.view.convert(itemNode.bounds, to: strongSelf.scrollNode.view) + itemNode.frame = projectedItemFrame + strongSelf.scrollNode.addSubnode(itemNode) + } + + strongSelf.reorderingItem = nil + strongSelf.reorderingItemPosition = nil + strongSelf.reorderingAutoScrollAnimator?.invalidate() + strongSelf.reorderingAutoScrollAnimator = nil + if let (size, sideInset, filters, selectedFilter, isReordering, isEditing, transitionFraction, presentationData) = strongSelf.currentParams { + strongSelf.update(size: size, sideInset: sideInset, filters: filters, selectedFilter: selectedFilter, isReordering: isReordering, isEditing: isEditing, transitionFraction: transitionFraction, presentationData: presentationData, transition: .animated(duration: 0.25, curve: .easeInOut)) + } + }, moved: { [weak self] offset in + guard let strongSelf = self, let reorderingItem = strongSelf.reorderingItem else { + return + } + if let reorderingItemNode = strongSelf.itemNodes[reorderingItem], let (initial, _) = strongSelf.reorderingItemPosition, let reorderedItemIds = strongSelf.reorderedItemIds, let currentItemIndex = reorderedItemIds.firstIndex(of: reorderingItem) { + + for (id, itemNode) in strongSelf.itemNodes { + guard let itemIndex = reorderedItemIds.firstIndex(of: id) else { + continue + } + if id != reorderingItem { + let itemFrame = itemNode.view.convert(itemNode.bounds, to: strongSelf.view) + if reorderingItemNode.frame.intersects(itemFrame) { + let targetIndex: Int + if reorderingItemNode.frame.midX < itemFrame.midX { + targetIndex = max(1, itemIndex - 1) + } else { + targetIndex = max(1, min(reorderedItemIds.count - 1, itemIndex)) + } + if targetIndex != currentItemIndex { + strongSelf.hapticFeedback.tap() + + var updatedReorderedItemIds = reorderedItemIds + if targetIndex > currentItemIndex { + updatedReorderedItemIds.insert(reorderingItem, at: targetIndex + 1) + updatedReorderedItemIds.remove(at: currentItemIndex) + } else { + updatedReorderedItemIds.remove(at: currentItemIndex) + updatedReorderedItemIds.insert(reorderingItem, at: targetIndex) + } + strongSelf.reorderedItemIds = updatedReorderedItemIds + if let (size, sideInset, filters, selectedFilter, isReordering, isEditing, transitionFraction, presentationData) = strongSelf.currentParams { + strongSelf.update(size: size, sideInset: sideInset, filters: filters, selectedFilter: selectedFilter, isReordering: isReordering, isEditing: isEditing, transitionFraction: transitionFraction, presentationData: presentationData, transition: .animated(duration: 0.25, curve: .easeInOut)) + } + } + break + } + } + } + + strongSelf.reorderingItemPosition = (initial, offset) + } + if let (size, sideInset, filters, selectedFilter, isReordering, isEditing, transitionFraction, presentationData) = strongSelf.currentParams { + strongSelf.update(size: size, sideInset: sideInset, filters: filters, selectedFilter: selectedFilter, isReordering: isReordering, isEditing: isEditing, transitionFraction: transitionFraction, presentationData: presentationData, transition: .immediate) + } + }) + self.reorderingGesture = reorderingGesture + self.view.addGestureRecognizer(reorderingGesture) + reorderingGesture.isEnabled = false } - func update(size: CGSize, sideInset: CGFloat, filters: [ChatListFilterTabEntry], selectedFilter: ChatListFilterTabEntryId?, presentationData: PresentationData, transition: ContainedViewLayoutTransition) { - let focusOnSelectedFilter = self.currentParams?.selectedFilter != selectedFilter - var previousSelectedAbsFrame: CGRect? + private var previousSelectedAbsFrame: CGRect? + private var previousSelectedFrame: CGRect? + + func cancelAnimations() { + self.selectedLineNode.layer.removeAllAnimations() + self.scrollNode.layer.removeAllAnimations() + } + + func update(size: CGSize, sideInset: CGFloat, filters: [ChatListFilterTabEntry], selectedFilter: ChatListFilterTabEntryId?, isReordering: Bool, isEditing: Bool, transitionFraction: CGFloat, presentationData: PresentationData, transition: ContainedViewLayoutTransition) { + var focusOnSelectedFilter = self.currentParams?.selectedFilter != selectedFilter let previousScrollBounds = self.scrollNode.bounds - if let currentSelectedFilter = self.currentParams?.selectedFilter, let itemNode = self.itemNodes[currentSelectedFilter] { - previousSelectedAbsFrame = itemNode.frame.offsetBy(dx: -self.scrollNode.bounds.minX, dy: 0.0) - } + let previousContentWidth = self.scrollNode.view.contentSize.width if self.currentParams?.presentationData.theme !== presentationData.theme { self.selectedLineNode.image = generateImage(CGSize(width: 7.0, height: 4.0), rotatedContext: { size, context in @@ -273,7 +562,30 @@ final class ChatListFilterTabContainerNode: ASDisplayNode { })?.stretchableImage(withLeftCapWidth: 4, topCapHeight: 1) } - self.currentParams = (size: size, sideInset: sideInset, filters: filters, selectedFilter: selectedFilter, presentationData: presentationData) + if isReordering { + if let reorderedItemIds = self.reorderedItemIds { + let currentIds = Set(reorderedItemIds) + if currentIds != Set(filters.map { $0.id }) { + var updatedReorderedItemIds = reorderedItemIds.filter { id in + return filters.contains(where: { $0.id == id }) + } + for filter in filters { + if !currentIds.contains(filter.id) { + updatedReorderedItemIds.append(filter.id) + } + } + self.reorderedItemIds = updatedReorderedItemIds + } + } else { + self.reorderedItemIds = filters.map { $0.id } + } + } else if self.reorderedItemIds != nil { + self.reorderedItemIds = nil + } + + self.currentParams = (size: size, sideInset: sideInset, filters: filters, selectedFilter: selectedFilter, isReordering, isEditing, transitionFraction, presentationData: presentationData) + + self.reorderingGesture?.isEnabled = isEditing || isReordering transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: size)) @@ -284,15 +596,30 @@ final class ChatListFilterTabContainerNode: ASDisplayNode { var badgeAnimations: [ChatListFilterTabEntryId: BadgeAnimation] = [:] - for filter in filters { + var reorderedFilters: [ChatListFilterTabEntry] = filters + if let reorderedItemIds = self.reorderedItemIds { + reorderedFilters = reorderedItemIds.compactMap { id -> ChatListFilterTabEntry? in + if let index = filters.firstIndex(where: { $0.id == id }) { + return filters[index] + } else { + return nil + } + } + } + + for filter in reorderedFilters { let itemNode: ItemNode + var itemNodeTransition = transition var wasAdded = false if let current = self.itemNodes[filter.id] { itemNode = current } else { + itemNodeTransition = .immediate wasAdded = true itemNode = ItemNode(pressed: { [weak self] in self?.tabSelected?(filter.id) + }, requestedDeletion: { [weak self] in + self?.tabRequestedDeletion?(filter.id) }, contextGesture: { [weak self] sourceNode, gesture in guard let strongSelf = self else { return @@ -321,7 +648,7 @@ final class ChatListFilterTabContainerNode: ASDisplayNode { if !wasAdded && (itemNode.unreadCount != 0) != (unreadCount != 0) { badgeAnimations[filter.id] = (unreadCount != 0) ? .in : .out } - itemNode.updateText(title: filter.title(strings: presentationData.strings), unreadCount: unreadCount, isNoFilter: isNoFilter, isSelected: selectedFilter == filter.id, presentationData: presentationData) + itemNode.updateText(title: filter.title(strings: presentationData.strings), shortTitle: filter.shortTitle(strings: presentationData.strings), unreadCount: unreadCount, isNoFilter: isNoFilter, isSelected: selectedFilter == filter.id, isEditing: false, isAllChats: isNoFilter, isReordering: isEditing || isReordering, presentationData: presentationData, transition: itemNodeTransition) } var removeKeys: [ChatListFilterTabEntryId] = [] for (id, _) in self.itemNodes { @@ -331,25 +658,31 @@ final class ChatListFilterTabContainerNode: ASDisplayNode { } for id in removeKeys { if let itemNode = self.itemNodes.removeValue(forKey: id) { - itemNode.removeFromSupernode() + transition.updateAlpha(node: itemNode, alpha: 0.0, completion: { [weak itemNode] _ in + itemNode?.removeFromSupernode() + }) + transition.updateTransformScale(node: itemNode, scale: 0.1) } } - var tabSizes: [(CGSize, ItemNode, Bool)] = [] + var tabSizes: [(ChatListFilterTabEntryId, CGSize, CGSize, ItemNode, Bool)] = [] var totalRawTabSize: CGFloat = 0.0 var selectionFrames: [CGRect] = [] - for filter in filters { + for filter in reorderedFilters { guard let itemNode = self.itemNodes[filter.id] else { continue } let wasAdded = itemNode.supernode == nil + var itemNodeTransition = transition if wasAdded { + itemNodeTransition = .immediate self.scrollNode.addSubnode(itemNode) } - let paneNodeWidth = itemNode.updateLayout(height: size.height, transition: transition) + let (paneNodeWidth, paneNodeShortWidth) = itemNode.updateLayout(height: size.height, transition: itemNodeTransition) let paneNodeSize = CGSize(width: paneNodeWidth, height: size.height) - tabSizes.append((paneNodeSize, itemNode, wasAdded)) + let paneNodeShortSize = CGSize(width: paneNodeShortWidth, height: size.height) + tabSizes.append((filter.id, paneNodeSize, paneNodeShortSize, itemNode, wasAdded)) totalRawTabSize += paneNodeSize.width if case .animated = transition, let badgeAnimation = badgeAnimations[filter.id] { @@ -362,38 +695,65 @@ final class ChatListFilterTabContainerNode: ASDisplayNode { } } - let minSpacing: CGFloat = 30.0 + let minSpacing: CGFloat = 26.0 - let sideInset: CGFloat = 16.0 - var leftOffset: CGFloat = sideInset + let resolvedSideInset: CGFloat = 16.0 + sideInset + var leftOffset: CGFloat = resolvedSideInset + + var longTitlesWidth: CGFloat = resolvedSideInset for i in 0 ..< tabSizes.count { - let (paneNodeSize, paneNode, wasAdded) = tabSizes[i] - let paneFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - paneNodeSize.height) / 2.0)), size: paneNodeSize) - if wasAdded { - paneNode.frame = paneFrame - paneNode.alpha = 0.0 - transition.updateAlpha(node: paneNode, alpha: 1.0) - } else { - transition.updateFrameAdditive(node: paneNode, frame: paneFrame) + let (itemId, paneNodeSize, paneNodeShortSize, paneNode, wasAdded) = tabSizes[i] + longTitlesWidth += paneNodeSize.width + if i != tabSizes.count - 1 { + longTitlesWidth += minSpacing } - paneNode.updateArea(size: paneFrame.size, sideInset: minSpacing / 2.0) + } + longTitlesWidth += resolvedSideInset + let useShortTitles = longTitlesWidth > size.width + + for i in 0 ..< tabSizes.count { + let (itemId, paneNodeLongSize, paneNodeShortSize, paneNode, wasAdded) = tabSizes[i] + var itemNodeTransition = transition + if wasAdded { + itemNodeTransition = .immediate + } + + let useShortTitle = itemId == .all && useShortTitles + let paneNodeSize = useShortTitle ? paneNodeShortSize : paneNodeLongSize + + let paneFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - paneNodeSize.height) / 2.0)), size: paneNodeSize) + + if itemId == self.reorderingItem, let (initial, offset) = self.reorderingItemPosition { + itemNodeTransition.updateSublayerTransformScale(node: paneNode, scale: 1.2) + itemNodeTransition.updateAlpha(node: paneNode, alpha: 0.9) + itemNodeTransition.updateFrameAdditive(node: paneNode, frame: CGRect(origin: CGPoint(x: initial + offset, y: paneFrame.minY), size: paneFrame.size)) + } else { + itemNodeTransition.updateSublayerTransformScale(node: paneNode, scale: 1.0) + itemNodeTransition.updateAlpha(node: paneNode, alpha: 1.0) + if wasAdded { + paneNode.frame = paneFrame + paneNode.alpha = 0.0 + itemNodeTransition.updateAlpha(node: paneNode, alpha: 1.0) + } else { + itemNodeTransition.updateFrameAdditive(node: paneNode, frame: paneFrame) + } + } + paneNode.updateArea(size: paneFrame.size, sideInset: minSpacing / 2.0, useShortTitle: useShortTitle, transition: itemNodeTransition) paneNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: -minSpacing / 2.0, bottom: 0.0, right: -minSpacing / 2.0) selectionFrames.append(paneFrame) leftOffset += paneNodeSize.width + minSpacing } + leftOffset -= minSpacing + leftOffset += resolvedSideInset - let addSize = CGSize(width: 32.0, height: size.height) - transition.updateFrame(node: self.addNode, frame: CGRect(origin: CGPoint(x: max(leftOffset, size.width - sideInset - addSize.width + 6.0), y: 0.0), size: addSize)) - self.addNode.update(size: addSize, theme: presentationData.theme) - leftOffset += addSize.width + minSpacing + self.scrollNode.view.contentSize = CGSize(width: leftOffset, height: size.height) - self.scrollNode.view.contentSize = CGSize(width: leftOffset - minSpacing + sideInset - 5.0, height: size.height) - - let transitionFraction: CGFloat = 0.0 + var previousFrame: CGRect? + var nextFrame: CGRect? var selectedFrame: CGRect? - if let selectedFilter = selectedFilter, let currentIndex = filters.index(where: { $0.id == selectedFilter }) { + if let selectedFilter = selectedFilter, let currentIndex = reorderedFilters.firstIndex(where: { $0.id == selectedFilter }) { func interpolateFrame(from fromValue: CGRect, to toValue: CGRect, t: CGFloat) -> CGRect { return CGRect(x: floorToScreenPixels(toValue.origin.x * t + fromValue.origin.x * (1.0 - t)), y: floorToScreenPixels(toValue.origin.y * t + fromValue.origin.y * (1.0 - t)), width: floorToScreenPixels(toValue.size.width * t + fromValue.size.width * (1.0 - t)), height: floorToScreenPixels(toValue.size.height * t + fromValue.size.height * (1.0 - t))) } @@ -418,32 +778,168 @@ final class ChatListFilterTabContainerNode: ASDisplayNode { if wasAdded { self.selectedLineNode.frame = lineFrame self.selectedLineNode.alpha = 0.0 - transition.updateAlpha(node: self.selectedLineNode, alpha: 1.0) } else { transition.updateFrame(node: self.selectedLineNode, frame: lineFrame) } - if focusOnSelectedFilter { - if selectedFilter == filters.first?.id { - transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(), size: self.scrollNode.bounds.size)) - } else if selectedFilter == filters.last?.id { - transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: max(0.0, self.scrollNode.view.contentSize.width - self.scrollNode.bounds.width), y: 0.0), size: self.scrollNode.bounds.size)) + transition.updateAlpha(node: self.selectedLineNode, alpha: isReordering && selectedFilter == .all ? 0.5 : 1.0) + + if let previousSelectedFrame = self.previousSelectedFrame { + let previousContentOffsetX = max(0.0, min(previousContentWidth - previousScrollBounds.width, floor(previousSelectedFrame.midX - previousScrollBounds.width / 2.0))) + if abs(previousContentOffsetX - previousScrollBounds.minX) < 1.0 { + focusOnSelectedFilter = true + } + } + + if focusOnSelectedFilter && self.reorderingItem == nil { + let updatedBounds: CGRect + if transitionFraction.isZero && selectedFilter == reorderedFilters.first?.id { + updatedBounds = CGRect(origin: CGPoint(), size: self.scrollNode.bounds.size) + } else if transitionFraction.isZero && selectedFilter == reorderedFilters.last?.id { + updatedBounds = CGRect(origin: CGPoint(x: max(0.0, self.scrollNode.view.contentSize.width - self.scrollNode.bounds.width), y: 0.0), size: self.scrollNode.bounds.size) } else { let contentOffsetX = max(0.0, min(self.scrollNode.view.contentSize.width - self.scrollNode.bounds.width, floor(selectedFrame.midX - self.scrollNode.bounds.width / 2.0))) - transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: contentOffsetX, y: 0.0), size: self.scrollNode.bounds.size)) + updatedBounds = CGRect(origin: CGPoint(x: contentOffsetX, y: 0.0), size: self.scrollNode.bounds.size) } - } else if !wasAdded, let previousSelectedAbsFrame = previousSelectedAbsFrame { - let contentOffsetX: CGFloat - if previousScrollBounds.minX.isZero { - contentOffsetX = 0.0 - } else if previousScrollBounds.maxX == previousScrollBounds.width { - contentOffsetX = self.scrollNode.view.contentSize.width - self.scrollNode.bounds.width - } else { - contentOffsetX = selectedFrame.midX - previousSelectedAbsFrame.midX - } - transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: contentOffsetX, y: 0.0), size: self.scrollNode.bounds.size)) + self.scrollNode.bounds = updatedBounds } + transition.animateHorizontalOffsetAdditive(node: self.scrollNode, offset: previousScrollBounds.minX - self.scrollNode.bounds.minX) + + self.previousSelectedAbsFrame = selectedFrame.offsetBy(dx: -self.scrollNode.bounds.minX, dy: 0.0) + self.previousSelectedFrame = selectedFrame } else { self.selectedLineNode.isHidden = true + self.previousSelectedAbsFrame = nil + self.previousSelectedFrame = nil + } + } +} + +private class ReorderingGestureRecognizerTimerTarget: NSObject { + private let f: () -> Void + + init(_ f: @escaping () -> Void) { + self.f = f + + super.init() + } + + @objc func timerEvent() { + self.f() + } +} + +private final class ReorderingGestureRecognizer: UIGestureRecognizer, UIGestureRecognizerDelegate { + private let shouldBegin: (CGPoint) -> Bool + private let began: (CGPoint) -> Void + private let ended: () -> Void + private let moved: (CGFloat) -> Void + + private var initialLocation: CGPoint? + private var delayTimer: Foundation.Timer? + + var currentLocation: CGPoint? + + init(shouldBegin: @escaping (CGPoint) -> Bool, began: @escaping (CGPoint) -> Void, ended: @escaping () -> Void, moved: @escaping (CGFloat) -> Void) { + self.shouldBegin = shouldBegin + self.began = began + self.ended = ended + self.moved = moved + + super.init(target: nil, action: nil) + + self.delegate = self + } + + override func reset() { + super.reset() + + self.initialLocation = nil + self.delayTimer?.invalidate() + self.delayTimer = nil + self.currentLocation = nil + } + + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool { + if otherGestureRecognizer is UIPanGestureRecognizer { + return true + } else { + return false + } + } + + override func touchesBegan(_ touches: Set, with event: UIEvent) { + super.touchesBegan(touches, with: event) + + guard let location = touches.first?.location(in: self.view) else { + self.state = .failed + return + } + + if self.state == .possible { + if self.delayTimer == nil { + if !self.shouldBegin(location) { + self.state = .failed + return + } + self.initialLocation = location + let timer = Foundation.Timer(timeInterval: 0.2, target: ReorderingGestureRecognizerTimerTarget { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.delayTimer = nil + strongSelf.state = .began + strongSelf.began(location) + }, selector: #selector(ReorderingGestureRecognizerTimerTarget.timerEvent), userInfo: nil, repeats: false) + self.delayTimer = timer + RunLoop.main.add(timer, forMode: .common) + } else { + self.state = .failed + } + } + } + + override func touchesEnded(_ touches: Set, with event: UIEvent) { + super.touchesEnded(touches, with: event) + + self.delayTimer?.invalidate() + + if self.state == .began || self.state == .changed { + self.ended() + } + + self.state = .failed + } + + override func touchesCancelled(_ touches: Set, with event: UIEvent) { + super.touchesCancelled(touches, with: event) + + if self.state == .began || self.state == .changed { + self.delayTimer?.invalidate() + self.ended() + self.state = .failed + } + } + + override func touchesMoved(_ touches: Set, with event: UIEvent) { + super.touchesMoved(touches, with: event) + + guard let initialLocation = self.initialLocation, let location = touches.first?.location(in: self.view) else { + return + } + let offset = location.x - initialLocation.x + self.currentLocation = location + + if self.delayTimer != nil { + if abs(offset) > 4.0 { + self.delayTimer?.invalidate() + self.state = .failed + return + } + } else { + if self.state == .began || self.state == .changed { + self.state = .changed + self.moved(offset) + } } } } diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index 4b60962349..87ab517a47 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -1003,6 +1003,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo self?.listNode.clearHighlightAnimated(true) }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in + }, additionalCategorySelected: { _ in }, messageSelected: { [weak self] peer, message, _ in self?.view.endEditing(true) if let peer = message.peers[message.id.peerId] { diff --git a/submodules/ChatListUI/Sources/ChatListUI.h b/submodules/ChatListUI/Sources/ChatListUI.h deleted file mode 100644 index 4a3e46fa3c..0000000000 --- a/submodules/ChatListUI/Sources/ChatListUI.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// ChatListUI.h -// ChatListUI -// -// Created by Peter on 8/13/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for ChatListUI. -FOUNDATION_EXPORT double ChatListUIVersionNumber; - -//! Project version string for ChatListUI. -FOUNDATION_EXPORT const unsigned char ChatListUIVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/ChatListUI/Sources/Node/ChatListHoleItem.swift b/submodules/ChatListUI/Sources/Node/ChatListHoleItem.swift index ccce665a4c..f69ff4eb35 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListHoleItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListHoleItem.swift @@ -6,8 +6,6 @@ import Display import SwiftSignalKit import TelegramPresentationData -private let titleFont = Font.regular(17.0) - class ChatListHoleItem: ListViewItem { let theme: PresentationTheme @@ -45,9 +43,6 @@ class ChatListHoleItem: ListViewItem { Queue.mainQueue().async { completion(nodeLayout, { _ in apply() - if let nodeValue = node() as? ChatListHoleItemNode { - nodeValue.updateBackgroundAndSeparatorsLayout() - } }) } } @@ -56,25 +51,11 @@ class ChatListHoleItem: ListViewItem { } } -private let separatorHeight = 1.0 / UIScreen.main.scale - class ChatListHoleItemNode: ListViewItemNode { - let separatorNode: ASDisplayNode - let labelNode: TextNode - var relativePosition: (first: Bool, last: Bool) = (false, false) required init() { - self.separatorNode = ASDisplayNode() - self.separatorNode.backgroundColor = UIColor(rgb: 0xc8c7cc) - self.separatorNode.isLayerBacked = true - - self.labelNode = TextNode() - super.init(layerBacked: false, dynamicBounce: false) - - self.addSubnode(self.separatorNode) - self.addSubnode(self.labelNode) } override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { @@ -84,45 +65,17 @@ class ChatListHoleItemNode: ListViewItemNode { } func asyncLayout() -> (_ item: ChatListHoleItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool) -> (ListViewItemNodeLayout, () -> Void) { - let labelNodeLayout = TextNode.asyncLayout(self.labelNode) - return { item, params, first, last in - let baseWidth = params.width - params.leftInset - params.rightInset - - let (labelLayout, labelApply) = labelNodeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "", font: titleFont, textColor: item.theme.chatList.messageTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: baseWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - - let insets = ChatListItemNode.insets(first: first, last: last, firstWithHeader: false) - let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 68.0), insets: insets) - - let separatorInset: CGFloat - if last { - separatorInset = 0.0 - } else { - separatorInset = 80.0 + params.leftInset - } + let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 0.0), insets: UIEdgeInsets()) return (layout, { [weak self] in if let strongSelf = self { strongSelf.relativePosition = (first, last) - let _ = labelApply() - - strongSelf.separatorNode.backgroundColor = item.theme.chatList.itemSeparatorColor - - strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: floor((params.width - labelLayout.size.width) / 2.0), y: floor((layout.contentSize.height - labelLayout.size.height) / 2.0)), size: labelLayout.size) - - strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: separatorInset, y: 68.0 - separatorHeight), size: CGSize(width: params.width - separatorInset, height: separatorHeight)) - strongSelf.contentSize = layout.contentSize strongSelf.insets = layout.insets - strongSelf.updateBackgroundAndSeparatorsLayout() } }) } } - - func updateBackgroundAndSeparatorsLayout() { - //let size = self.bounds.size - //let insets = self.insets - } } diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 33615bf3cb..b63019d418 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -1665,7 +1665,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let leftInset: CGFloat = params.leftInset + avatarLeftInset - let rawContentRect = CGRect(origin: CGPoint(x: 2.0, y: layoutOffset + 8.0), size: CGSize(width: params.width - leftInset - params.rightInset - 10.0 - 1.0 - editingOffset, height: self.bounds.size.height - 12.0 - 9.0)) + let rawContentRect = CGRect(origin: CGPoint(x: 2.0, y: layoutOffset + 8.0), size: CGSize(width: params.width - leftInset - params.rightInset - 10.0 - editingOffset, height: self.bounds.size.height - 12.0 - 9.0)) let contentRect = rawContentRect.offsetBy(dx: editingOffset + leftInset + offset, dy: 0.0) diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 6d2f4b5903..76e06b45d8 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -14,10 +14,11 @@ import ContactsPeerItem import ContextUI import ItemListUI import SearchUI +import ChatListSearchItemHeader public enum ChatListNodeMode { case chatList - case peers(filter: ChatListNodePeersFilter) + case peers(filter: ChatListNodePeersFilter, isSelecting: Bool, additionalCategories: [ChatListNodeAdditionalCategory]) } struct ChatListNodeListViewTransition { @@ -29,6 +30,7 @@ struct ChatListNodeListViewTransition { let scrollToItem: ListViewScrollToItem? let stationaryItemRange: (Int, Int)? let adjustScrollToFirstItem: Bool + let animateCrossfade: Bool } final class ChatListHighlightedLocation { @@ -50,6 +52,7 @@ public final class ChatListNodeInteraction { let peerSelected: (Peer) -> Void let disabledPeerSelected: (Peer) -> Void let togglePeerSelected: (PeerId) -> Void + let additionalCategorySelected: (Int) -> Void let messageSelected: (Peer, Message, Bool) -> Void let groupSelected: (PeerGroupId) -> Void let addContact: (String) -> Void @@ -66,11 +69,12 @@ public final class ChatListNodeInteraction { public var searchTextHighightState: String? var highlightedChatLocation: ChatListHighlightedLocation? - public init(activateSearch: @escaping () -> Void, peerSelected: @escaping (Peer) -> Void, disabledPeerSelected: @escaping (Peer) -> Void, togglePeerSelected: @escaping (PeerId) -> Void, messageSelected: @escaping (Peer, Message, Bool) -> Void, groupSelected: @escaping (PeerGroupId) -> Void, addContact: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setItemPinned: @escaping (PinnedItemId, Bool) -> Void, setPeerMuted: @escaping (PeerId, Bool) -> Void, deletePeer: @escaping (PeerId) -> Void, updatePeerGrouping: @escaping (PeerId, Bool) -> Void, togglePeerMarkedUnread: @escaping (PeerId, Bool) -> Void, toggleArchivedFolderHiddenByDefault: @escaping () -> Void, activateChatPreview: @escaping (ChatListItem, ASDisplayNode, ContextGesture?) -> Void, present: @escaping (ViewController) -> Void) { + public init(activateSearch: @escaping () -> Void, peerSelected: @escaping (Peer) -> Void, disabledPeerSelected: @escaping (Peer) -> Void, togglePeerSelected: @escaping (PeerId) -> Void, additionalCategorySelected: @escaping (Int) -> Void, messageSelected: @escaping (Peer, Message, Bool) -> Void, groupSelected: @escaping (PeerGroupId) -> Void, addContact: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setItemPinned: @escaping (PinnedItemId, Bool) -> Void, setPeerMuted: @escaping (PeerId, Bool) -> Void, deletePeer: @escaping (PeerId) -> Void, updatePeerGrouping: @escaping (PeerId, Bool) -> Void, togglePeerMarkedUnread: @escaping (PeerId, Bool) -> Void, toggleArchivedFolderHiddenByDefault: @escaping () -> Void, activateChatPreview: @escaping (ChatListItem, ASDisplayNode, ContextGesture?) -> Void, present: @escaping (ViewController) -> Void) { self.activateSearch = activateSearch self.peerSelected = peerSelected self.disabledPeerSelected = disabledPeerSelected self.togglePeerSelected = togglePeerSelected + self.additionalCategorySelected = additionalCategorySelected self.messageSelected = messageSelected self.groupSelected = groupSelected self.addContact = addContact @@ -103,12 +107,14 @@ public struct ChatListNodeState: Equatable { public var pendingRemovalPeerIds: Set public var pendingClearHistoryPeerIds: Set public var archiveShouldBeTemporaryRevealed: Bool + public var selectedAdditionalCategoryIds: Set - public init(presentationData: ChatListPresentationData, editing: Bool, peerIdWithRevealedOptions: PeerId?, selectedPeerIds: Set, peerInputActivities: ChatListNodePeerInputActivities?, pendingRemovalPeerIds: Set, pendingClearHistoryPeerIds: Set, archiveShouldBeTemporaryRevealed: Bool) { + public init(presentationData: ChatListPresentationData, editing: Bool, peerIdWithRevealedOptions: PeerId?, selectedPeerIds: Set, selectedAdditionalCategoryIds: Set, peerInputActivities: ChatListNodePeerInputActivities?, pendingRemovalPeerIds: Set, pendingClearHistoryPeerIds: Set, archiveShouldBeTemporaryRevealed: Bool) { self.presentationData = presentationData self.editing = editing self.peerIdWithRevealedOptions = peerIdWithRevealedOptions self.selectedPeerIds = selectedPeerIds + self.selectedAdditionalCategoryIds = selectedAdditionalCategoryIds self.peerInputActivities = peerInputActivities self.pendingRemovalPeerIds = pendingRemovalPeerIds self.pendingClearHistoryPeerIds = pendingClearHistoryPeerIds @@ -128,6 +134,9 @@ public struct ChatListNodeState: Equatable { if lhs.selectedPeerIds != rhs.selectedPeerIds { return false } + if lhs.selectedAdditionalCategoryIds != rhs.selectedAdditionalCategoryIds { + return false + } if lhs.peerInputActivities !== rhs.peerInputActivities { return false } @@ -149,11 +158,22 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL switch entry.entry { case .HeaderEntry: return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListEmptyHeaderItem(), directionHint: entry.directionHint) - case let .PeerEntry(index, presentationData, message, combinedReadState, notificationSettings, embeddedState, peer, presence, summaryInfo, editing, hasActiveRevealControls, selected, inputActivities, isAd, hasFailedMessages): + case let .AdditionalCategory(_, id, title, image, selected, presentationData): + return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListAdditionalCategoryItem( + presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings), + context: context, + title: title, + image: image, + isSelected: selected, + action: { + nodeInteraction.additionalCategorySelected(id) + } + ), directionHint: entry.directionHint) + case let .PeerEntry(index, presentationData, message, combinedReadState, notificationSettings, embeddedState, peer, presence, summaryInfo, editing, hasActiveRevealControls, selected, inputActivities, isAd, hasFailedMessages, isContact): switch mode { case .chatList: return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, isInFilter: isInFilter, index: index, content: .peer(message: message, peer: peer, combinedReadState: combinedReadState, notificationSettings: notificationSettings, presence: presence, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, isAd: isAd, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: hasFailedMessages), editing: editing, hasActiveRevealControls: hasActiveRevealControls, selected: selected, header: nil, enableContextActions: true, hiddenOffset: false, interaction: nodeInteraction), directionHint: entry.directionHint) - case let .peers(filter): + case let .peers(filter, isSelecting, _): let itemPeer = peer.chatMainPeer var chatPeer: Peer? if let peer = peer.peers[peer.peerId] { @@ -217,8 +237,23 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL } } } + + var header: ChatListSearchItemHeader? + switch mode { + case let .peers(_, _, additionalCategories): + if !additionalCategories.isEmpty { + header = ChatListSearchItemHeader(type: .chats, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) + } + default: + break + } + + var status: ContactsPeerItemStatus = .none + if isSelecting, let itemPeer = itemPeer { + status = .custom(statusStringForPeerType(strings: presentationData.strings, peer: itemPeer, isContact: isContact)) + } - return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings), sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, context: context, peerMode: .generalSearch, peer: .peer(peer: itemPeer, chatPeer: chatPeer), status: .none, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { _ in + return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings), sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, context: context, peerMode: .generalSearch, peer: .peer(peer: itemPeer, chatPeer: chatPeer), status: status, enabled: enabled, selection: editing ? .selectable(selected: selected) : .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { _ in if let chatPeer = chatPeer { nodeInteraction.peerSelected(chatPeer) } @@ -241,11 +276,11 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, peerGroupId: PeerGroupId, isInFilter: Bool, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionUpdateEntry]) -> [ListViewUpdateItem] { return entries.map { entry -> ListViewUpdateItem in switch entry.entry { - case let .PeerEntry(index, presentationData, message, combinedReadState, notificationSettings, embeddedState, peer, presence, summaryInfo, editing, hasActiveRevealControls, selected, inputActivities, isAd, hasFailedMessages): + case let .PeerEntry(index, presentationData, message, combinedReadState, notificationSettings, embeddedState, peer, presence, summaryInfo, editing, hasActiveRevealControls, selected, inputActivities, isAd, hasFailedMessages, isContact): switch mode { case .chatList: return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, isInFilter: isInFilter, index: index, content: .peer(message: message, peer: peer, combinedReadState: combinedReadState, notificationSettings: notificationSettings, presence: presence, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, isAd: isAd, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: hasFailedMessages), editing: editing, hasActiveRevealControls: hasActiveRevealControls, selected: selected, header: nil, enableContextActions: true, hiddenOffset: false, interaction: nodeInteraction), directionHint: entry.directionHint) - case let .peers(filter): + case let .peers(filter, isSelecting, _): let itemPeer = peer.chatMainPeer var chatPeer: Peer? if let peer = peer.peers[peer.peerId] { @@ -266,7 +301,22 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL enabled = false } } - return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings), sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, context: context, peerMode: .generalSearch, peer: .peer(peer: itemPeer, chatPeer: chatPeer), status: .none, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { _ in + var header: ChatListSearchItemHeader? + switch mode { + case let .peers(_, _, additionalCategories): + if !additionalCategories.isEmpty { + header = ChatListSearchItemHeader(type: .chats, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) + } + default: + break + } + + var status: ContactsPeerItemStatus = .none + if isSelecting, let itemPeer = itemPeer { + status = .custom(statusStringForPeerType(strings: presentationData.strings, peer: itemPeer, isContact: isContact)) + } + + return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings), sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, context: context, peerMode: .generalSearch, peer: .peer(peer: itemPeer, chatPeer: chatPeer), status: status, enabled: enabled, selection: editing ? .selectable(selected: selected) : .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { _ in if let chatPeer = chatPeer { nodeInteraction.peerSelected(chatPeer) } @@ -284,12 +334,23 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint) case .HeaderEntry: return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListEmptyHeaderItem(), directionHint: entry.directionHint) + case let .AdditionalCategory(index: _, id, title, image, selected, presentationData): + return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListAdditionalCategoryItem( + presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings), + context: context, + title: title, + image: image, + isSelected: selected, + action: { + nodeInteraction.additionalCategorySelected(id) + } + ), directionHint: entry.directionHint) } } } private func mappedChatListNodeViewListTransition(context: AccountContext, nodeInteraction: ChatListNodeInteraction, peerGroupId: PeerGroupId, isInFilter: Bool, mode: ChatListNodeMode, transition: ChatListNodeViewTransition) -> ChatListNodeListViewTransition { - return ChatListNodeListViewTransition(chatListView: transition.chatListView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, nodeInteraction: nodeInteraction, peerGroupId: peerGroupId, isInFilter: isInFilter, mode: mode, entries: transition.insertEntries), updateItems: mappedUpdateEntries(context: context, nodeInteraction: nodeInteraction, peerGroupId: peerGroupId, isInFilter: isInFilter, mode: mode, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, adjustScrollToFirstItem: transition.adjustScrollToFirstItem) + return ChatListNodeListViewTransition(chatListView: transition.chatListView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, nodeInteraction: nodeInteraction, peerGroupId: peerGroupId, isInFilter: isInFilter, mode: mode, entries: transition.insertEntries), updateItems: mappedUpdateEntries(context: context, nodeInteraction: nodeInteraction, peerGroupId: peerGroupId, isInFilter: isInFilter, mode: mode, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, adjustScrollToFirstItem: transition.adjustScrollToFirstItem, animateCrossfade: transition.animateCrossfade) } private final class ChatListOpaqueTransactionState { @@ -329,11 +390,6 @@ public enum ChatListNodeEmptyState: Equatable { case empty(isLoading: Bool) } -enum ChatListNodePaneSwitchAnimationDirection { - case left - case right -} - public final class ChatListNode: ListView { private let controlsHistoryPreload: Bool private let context: AccountContext @@ -352,8 +408,9 @@ public final class ChatListNode: ListView { return _contentsReady.get() } - public var peerSelected: ((PeerId, Bool, Bool) -> Void)? + public var peerSelected: ((Peer, Bool, Bool) -> Void)? public var disabledPeerSelected: ((Peer) -> Void)? + public var additionalCategorySelected: ((Int) -> Void)? public var groupSelected: ((PeerGroupId) -> Void)? public var addContact: ((String) -> Void)? public var activateSearch: (() -> Void)? @@ -373,22 +430,19 @@ public final class ChatListNode: ListView { private var dequeuedInitialTransitionOnLayout = false private var enqueuedTransition: (ChatListNodeListViewTransition, () -> Void)? - private(set) var currentState: ChatListNodeState + public private(set) var currentState: ChatListNodeState private let statePromise: ValuePromise public var state: Signal { return self.statePromise.get() } - var paneSwitchAnimation: (ChatListNodePaneSwitchAnimationDirection, ContainedViewLayoutTransition)? private var currentLocation: ChatListNodeLocation? private(set) var chatListFilter: ChatListFilter? { didSet { self.chatListFilterValue.set(.single(self.chatListFilter)) if self.chatListFilter != oldValue { - if let currentLocation = self.currentLocation { - self.setChatListLocation(.initial(count: 50, filter: self.chatListFilter)) - } + self.setChatListLocation(.initial(count: 50, filter: self.chatListFilter)) } } } @@ -433,7 +487,7 @@ public final class ChatListNode: ListView { } } - var isEmptyUpdated: ((ChatListNodeEmptyState, Bool, ChatListNodePaneSwitchAnimationDirection?, ContainedViewLayoutTransition) -> Void)? + var isEmptyUpdated: ((ChatListNodeEmptyState, Bool, ContainedViewLayoutTransition) -> Void)? private var currentIsEmptyState: ChatListNodeEmptyState? public var addedVisibleChatsWithPeerIds: (([PeerId]) -> Void)? @@ -453,7 +507,12 @@ public final class ChatListNode: ListView { self.controlsHistoryPreload = controlsHistoryPreload self.mode = mode - self.currentState = ChatListNodeState(presentationData: ChatListPresentationData(theme: theme, fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, disableAnimations: disableAnimations), editing: false, peerIdWithRevealedOptions: nil, selectedPeerIds: Set(), peerInputActivities: nil, pendingRemovalPeerIds: Set(), pendingClearHistoryPeerIds: Set(), archiveShouldBeTemporaryRevealed: false) + var isSelecting = false + if case .peers(_, true, _) = mode { + isSelecting = true + } + + self.currentState = ChatListNodeState(presentationData: ChatListPresentationData(theme: theme, fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, disableAnimations: disableAnimations), editing: isSelecting, peerIdWithRevealedOptions: nil, selectedPeerIds: Set(), selectedAdditionalCategoryIds: Set(), peerInputActivities: nil, pendingRemovalPeerIds: Set(), pendingClearHistoryPeerIds: Set(), archiveShouldBeTemporaryRevealed: false) self.statePromise = ValuePromise(self.currentState, ignoreRepeated: true) self.theme = theme @@ -471,7 +530,7 @@ public final class ChatListNode: ListView { } }, peerSelected: { [weak self] peer in if let strongSelf = self, let peerSelected = strongSelf.peerSelected { - peerSelected(peer.id, true, false) + peerSelected(peer, true, false) } }, disabledPeerSelected: { [weak self] peer in if let strongSelf = self, let disabledPeerSelected = strongSelf.disabledPeerSelected { @@ -489,9 +548,11 @@ public final class ChatListNode: ListView { } return state } + }, additionalCategorySelected: { [weak self] id in + self?.additionalCategorySelected?(id) }, messageSelected: { [weak self] peer, message, isAd in if let strongSelf = self, let peerSelected = strongSelf.peerSelected { - peerSelected(peer.id, true, isAd) + peerSelected(peer, true, isAd) } }, groupSelected: { [weak self] groupId in if let strongSelf = self, let groupSelected = strongSelf.groupSelected { @@ -586,7 +647,7 @@ public final class ChatListNode: ListView { let currentRemovingPeerId = self.currentRemovingPeerId let savedMessagesPeer: Signal - if case let .peers(filter) = mode, filter.contains(.onlyWriteable) { + if case let .peers(filter, _, _) = mode, filter.contains(.onlyWriteable) { savedMessagesPeer = context.account.postbox.loadedPeerWithId(context.account.peerId) |> map(Optional.init) } else { @@ -635,11 +696,11 @@ public final class ChatListNode: ListView { let (rawEntries, isLoading) = chatListNodeEntriesForView(update.view, state: state, savedMessagesPeer: savedMessagesPeer, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, mode: mode) let entries = rawEntries.filter { entry in switch entry { - case let .PeerEntry(_, _, _, _, _, _, peer, _, _, _, _, _, _, _, _): + case let .PeerEntry(_, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _): switch mode { case .chatList: return true - case let .peers(filter): + case let .peers(filter, _, _): guard !filter.contains(.excludeSavedMessages) || peer.peerId != currentPeerId else { return false } guard !filter.contains(.excludeSecretChats) || peer.peerId.namespace != Namespaces.Peer.SecretChat else { return false } guard !filter.contains(.onlyPrivateChats) || peer.peerId.namespace == Namespaces.Peer.CloudUser else { return false } @@ -745,7 +806,7 @@ public final class ChatListNode: ListView { var didIncludeHiddenByDefaultArchive = false if let previous = previousView { for entry in previous.filteredEntries { - if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry { + if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry { if index.pinningIndex != nil { previousPinnedChats.append(index.messageIndex.id.peerId) } @@ -779,6 +840,9 @@ public final class ChatListNode: ListView { if previousState.selectedPeerIds != state.selectedPeerIds { disableAnimations = false } + if previousState.selectedAdditionalCategoryIds != state.selectedAdditionalCategoryIds { + disableAnimations = false + } if doesIncludeRemovingPeerId != didIncludeRemovingPeerId { disableAnimations = false } @@ -823,10 +887,10 @@ public final class ChatListNode: ListView { let originalView = chatListView.originalView if let range = range.loadedRange { var location: ChatListNodeLocation? - if range.firstIndex < 5 && originalView.laterIndex != nil { - location = .navigation(index: originalView.entries[originalView.entries.count - 1].index, filter: strongSelf.chatListFilter) - } else if range.firstIndex >= 5 && range.lastIndex >= originalView.entries.count - 5 && originalView.earlierIndex != nil { - location = .navigation(index: originalView.entries[0].index, filter: strongSelf.chatListFilter) + if range.firstIndex < 5, let laterIndex = originalView.laterIndex { + location = .navigation(index: laterIndex, filter: strongSelf.chatListFilter) + } else if range.firstIndex >= 5, range.lastIndex >= originalView.entries.count - 5, let earlierIndex = originalView.earlierIndex { + location = .navigation(index: earlierIndex, filter: strongSelf.chatListFilter) } if let location = location, location != strongSelf.currentLocation { @@ -847,7 +911,7 @@ public final class ChatListNode: ListView { continue } switch chatListView.filteredEntries[entryCount - i - 1] { - case let .PeerEntry(_, _, _, readState, notificationSettings, _, _, _, _, _, _, _, _, _, _): + case let .PeerEntry(_, _, _, readState, notificationSettings, _, _, _, _, _, _, _, _, _, _, _): if let readState = readState { let count = readState.count rawUnreadCount += count @@ -993,7 +1057,7 @@ public final class ChatListNode: ListView { var referenceId: PinnedItemId? var beforeAll = false switch toEntry { - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, isAd, _): + case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, isAd, _, _): if isAd { beforeAll = true } else { @@ -1005,13 +1069,13 @@ public final class ChatListNode: ListView { break } - if let _ = fromEntry.sortIndex.pinningIndex { + if case let .index(index) = fromEntry.sortIndex, let _ = index.pinningIndex { return strongSelf.context.account.postbox.transaction { transaction -> Bool in var itemIds = transaction.getPinnedItemIds(groupId: groupId) var itemId: PinnedItemId? switch fromEntry { - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): itemId = .peer(index.messageIndex.id.peerId) /*case let .GroupReferenceEntry(_, _, groupId, _, _, _, _): itemId = .group(groupId)*/ @@ -1180,15 +1244,10 @@ public final class ChatListNode: ListView { } private func resetFilter() { - if let chatListFilter = chatListFilter { - let preferencesKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.chatListFilters])) - self.updatedFilterDisposable.set((context.account.postbox.combinedView(keys: [preferencesKey]) - |> map { view -> ChatListFilter? in - guard let preferencesView = view.views[preferencesKey] as? PreferencesView else { - return nil - } - let filersState = preferencesView.values[PreferencesKeys.chatListFilters] as? ChatListFiltersState ?? ChatListFiltersState.default - for filter in filersState.filters { + if let chatListFilter = self.chatListFilter { + self.updatedFilterDisposable.set((updatedChatListFilters(postbox: self.context.account.postbox) + |> map { filters -> ChatListFilter? in + for filter in filters { if filter.id == chatListFilter.id { return filter } @@ -1263,13 +1322,6 @@ public final class ChatListNode: ListView { if let (transition, completion) = self.enqueuedTransition { self.enqueuedTransition = nil - let paneSwitchCopyView: UIView? - if let (direction, transition) = self.paneSwitchAnimation, let copyView = self.view.snapshotContentTree(unhide: false, keepTransform: true) { - paneSwitchCopyView = copyView - } else { - paneSwitchCopyView = nil - } - let completion: (ListViewDisplayedItemRange) -> Void = { [weak self] visibleRange in if let strongSelf = self { strongSelf.chatListView = transition.chatListView @@ -1278,7 +1330,7 @@ public final class ChatListNode: ListView { if case .chatList = strongSelf.mode { let entryCount = transition.chatListView.filteredEntries.count if entryCount >= 1 { - if transition.chatListView.filteredEntries[entryCount - 1].sortIndex.pinningIndex != nil { + if case let .index(index) = transition.chatListView.filteredEntries[entryCount - 1].sortIndex, index.pinningIndex != nil { pinnedOverscroll = true } } @@ -1308,20 +1360,37 @@ public final class ChatListNode: ListView { } var isEmpty = false + var isLoading = false if transition.chatListView.filteredEntries.isEmpty { isEmpty = true } else { if transition.chatListView.filteredEntries.count <= 2 { isEmpty = true - loop: for entry in transition.chatListView.filteredEntries { + loop1: for entry in transition.chatListView.filteredEntries { switch entry { - case .GroupReferenceEntry, .HeaderEntry: + case .GroupReferenceEntry, .HeaderEntry, .HoleEntry: break default: isEmpty = false - break loop + break loop1 } } + isLoading = true + var hasHoles = false + loop2: for entry in transition.chatListView.filteredEntries { + switch entry { + case .HoleEntry: + hasHoles = true + case .HeaderEntry: + break + default: + isLoading = false + break loop2 + } + } + if !hasHoles { + isLoading = false + } } } @@ -1329,16 +1398,16 @@ public final class ChatListNode: ListView { if transition.chatListView.isLoading { isEmptyState = .empty(isLoading: true) } else if isEmpty { - isEmptyState = .empty(isLoading: false) + isEmptyState = .empty(isLoading: isLoading) } else { var containsChats = false loop: for entry in transition.chatListView.filteredEntries { switch entry { - case .GroupReferenceEntry, .HoleEntry, .PeerEntry: - containsChats = true - break loop - case .ArchiveIntro, .HeaderEntry: - break + case .GroupReferenceEntry, .HoleEntry, .PeerEntry: + containsChats = true + break loop + case .ArchiveIntro, .HeaderEntry, .AdditionalCategory: + break } } isEmptyState = .notEmpty(containsChats: containsChats) @@ -1359,33 +1428,14 @@ public final class ChatListNode: ListView { strongSelf.addedVisibleChatsWithPeerIds?(insertedPeerIds) } - var isEmptyUpdate: (ChatListNodePaneSwitchAnimationDirection?, ContainedViewLayoutTransition) = (nil, .immediate) - if let (direction, transition) = strongSelf.paneSwitchAnimation { - strongSelf.paneSwitchAnimation = nil - if let copyView = paneSwitchCopyView { - let offset: CGFloat - switch direction { - case .left: - offset = -strongSelf.bounds.width - case .right: - offset = strongSelf.bounds.width - } - copyView.frame = strongSelf.bounds.offsetBy(dx: offset, dy: 0.0) - strongSelf.view.addSubview(copyView) - transition.animateHorizontalOffsetAdditive(node: strongSelf, offset: offset, completion: { [weak copyView] in - copyView?.removeFromSuperview() - }) - isEmptyUpdate = (direction, transition) - } - } else { - if transition.options.contains(.AnimateInsertion) { - isEmptyUpdate.1 = .animated(duration: 0.25, curve: .easeInOut) - } + var isEmptyUpdate: ContainedViewLayoutTransition = .immediate + if transition.options.contains(.AnimateInsertion) || transition.animateCrossfade { + isEmptyUpdate = .animated(duration: 0.25, curve: .easeInOut) } if strongSelf.currentIsEmptyState != isEmptyState { strongSelf.currentIsEmptyState = isEmptyState - strongSelf.isEmptyUpdated?(isEmptyState, transition.chatListView.filter != nil, isEmptyUpdate.0, isEmptyUpdate.1) + strongSelf.isEmptyUpdated?(isEmptyState, transition.chatListView.filter != nil, isEmptyUpdate) } if !strongSelf.hasUpdatedAppliedChatListFilterValueOnce || transition.chatListView.filter != strongSelf.currentAppliedChatListFilterValue { @@ -1422,6 +1472,35 @@ public final class ChatListNode: ListView { } } + var isNavigationHidden: Bool { + switch self.visibleContentOffset() { + case let .known(value) where abs(value) < navigationBarSearchContentHeight - 1.0: + return false + default: + return true + } + } + + func adjustScrollOffsetForNavigation(isNavigationHidden: Bool) { + if self.isNavigationHidden == isNavigationHidden { + return + } + var scrollToItem: ListViewScrollToItem? + switch self.visibleContentOffset() { + case let .known(value) where abs(value) < navigationBarSearchContentHeight - 1.0: + if isNavigationHidden { + scrollToItem = ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: false, curve: .Default(duration: 0.0), directionHint: .Up) + } + default: + if !isNavigationHidden { + scrollToItem = ListViewScrollToItem(index: 0, position: .top(0.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Up) + } + } + if let scrollToItem = scrollToItem { + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: scrollToItem, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + } + } + public func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets) { self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) @@ -1524,9 +1603,9 @@ public final class ChatListNode: ListView { } let entryCount = chatListView.filteredEntries.count - var current: (ChatListIndex, PeerId, Int)? = nil - var previous: (ChatListIndex, PeerId)? = nil - var next: (ChatListIndex, PeerId)? = nil + var current: (ChatListIndex, Peer, Int)? = nil + var previous: (ChatListIndex, Peer)? = nil + var next: (ChatListIndex, Peer)? = nil outer: for i in range.firstIndex ..< range.lastIndex { if i < 0 || i >= entryCount { @@ -1534,9 +1613,9 @@ public final class ChatListNode: ListView { continue } switch chatListView.filteredEntries[entryCount - i - 1] { - case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _): + case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _): if interaction.highlightedChatLocation?.location == ChatLocation.peer(peer.peerId) { - current = (index, peer.peerId, entryCount - i - 1) + current = (index, peer.peer!, entryCount - i - 1) break outer } default: @@ -1556,22 +1635,35 @@ public final class ChatListNode: ListView { } else { position = .later(than: nil) } - let _ = (relativeUnreadChatListIndex(position: position) |> deliverOnMainQueue).start(next: { [weak self] index in - guard let strongSelf = self, let index = index else { + let postbox = self.context.account.postbox + let _ = (relativeUnreadChatListIndex(position: position) + |> mapToSignal { index -> Signal<(ChatListIndex, Peer)?, NoError> in + if let index = index { + return postbox.transaction { transaction -> (ChatListIndex, Peer)? in + return transaction.getPeer(index.messageIndex.id.peerId).flatMap { peer -> (ChatListIndex, Peer)? in + (index, peer) + } + } + } else { + return .single(nil) + } + } + |> deliverOnMainQueue).start(next: { [weak self] indexAndPeer in + guard let strongSelf = self, let (index, peer) = indexAndPeer else { return } let location: ChatListNodeLocation = .scroll(index: index, sourceIndex: strongSelf.currentlyVisibleLatestChatListIndex() ?? .absoluteUpperBound, scrollPosition: .center(.top), animated: true, filter: strongSelf.chatListFilter) strongSelf.setChatListLocation(location) - strongSelf.peerSelected?(index.messageIndex.id.peerId, false, false) + strongSelf.peerSelected?(peer, false, false) }) case .previous(unread: false), .next(unread: false): - var target: (ChatListIndex, PeerId)? = nil + var target: (ChatListIndex, Peer)? = nil if let current = current, entryCount > 1 { - if current.2 > 0, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 - 1] { - next = (index, peer.peerId) + if current.2 > 0, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 - 1] { + next = (index, peer.peer!) } - if current.2 <= entryCount - 2, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 + 1] { - previous = (index, peer.peerId) + if current.2 <= entryCount - 2, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 + 1] { + previous = (index, peer.peer!) } if case .previous = option { target = previous @@ -1579,8 +1671,8 @@ public final class ChatListNode: ListView { target = next } } else if entryCount > 0 { - if case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[entryCount - 1] { - target = (index, peer.peerId) + if case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[entryCount - 1] { + target = (index, peer.peer!) } } if let target = target { @@ -1589,7 +1681,15 @@ public final class ChatListNode: ListView { self.peerSelected?(target.1, false, false) } case let .peerId(peerId): - self.peerSelected?(peerId, false, false) + let _ = (self.context.account.postbox.transaction { transaction -> Peer? in + return transaction.getPeer(peerId) + } + |> deliverOnMainQueue).start(next: { [weak self] peer in + guard let strongSelf = self, let peer = peer else { + return + } + strongSelf.peerSelected?(peer, false, false) + }) case let .index(index): guard index < 10 else { return @@ -1604,10 +1704,10 @@ public final class ChatListNode: ListView { |> take(1) |> deliverOnMainQueue).start(next: { update in let entries = update.view.entries - if entries.count > index, case let .MessageEntry(index, _, _, _, _, renderedPeer, _, _, _) = entries[10 - index - 1] { + if entries.count > index, case let .MessageEntry(index, _, _, _, _, renderedPeer, _, _, _, _) = entries[10 - index - 1] { let location: ChatListNodeLocation = .scroll(index: index, sourceIndex: .absoluteLowerBound, scrollPosition: .center(.top), animated: true, filter: filter) self.setChatListLocation(location) - self.peerSelected?(renderedPeer.peerId, false, false) + self.peerSelected?(renderedPeer.peer!, false, false) } }) }) @@ -1647,7 +1747,7 @@ public final class ChatListNode: ListView { continue } switch chatListView.filteredEntries[entryCount - i - 1] { - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return index default: break @@ -1657,3 +1757,31 @@ public final class ChatListNode: ListView { return nil } } + +//TODO:localize +private func statusStringForPeerType(strings: PresentationStrings, peer: Peer, isContact: Bool) -> String { + if let user = peer as? TelegramUser { + if user.botInfo != nil || user.flags.contains(.isSupport) { + return "bot" + } else if isContact { + return "contact" + } else { + return "user" + } + } else if peer is TelegramSecretChat { + if isContact { + return "contact" + } else { + return "user" + } + } else if peer is TelegramGroup { + return "group" + } else if let channel = peer as? TelegramChannel { + if case .group = channel.info { + return "group" + } else { + return "channel" + } + } + return "user" +} diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index 2da2b52acf..cb0eca58a3 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import Postbox import TelegramCore import SyncCore @@ -11,27 +12,55 @@ enum ChatListNodeEntryId: Hashable { case PeerId(Int64) case GroupId(PeerGroupId) case ArchiveIntro + case additionalCategory(Int) +} + +enum ChatListNodeEntrySortIndex: Comparable { + case index(ChatListIndex) + case additionalCategory(Int) + + static func <(lhs: ChatListNodeEntrySortIndex, rhs: ChatListNodeEntrySortIndex) -> Bool { + switch lhs { + case let .index(lhsIndex): + switch rhs { + case let .index(rhsIndex): + return lhsIndex < rhsIndex + case .additionalCategory: + return false + } + case let .additionalCategory(lhsIndex): + switch rhs { + case let .additionalCategory(rhsIndex): + return lhsIndex < rhsIndex + case .index: + return true + } + } + } } enum ChatListNodeEntry: Comparable, Identifiable { case HeaderEntry - case PeerEntry(index: ChatListIndex, presentationData: ChatListPresentationData, message: Message?, readState: CombinedPeerReadState?, notificationSettings: PeerNotificationSettings?, embeddedInterfaceState: PeerChatListEmbeddedInterfaceState?, peer: RenderedPeer, presence: PeerPresence?, summaryInfo: ChatListMessageTagSummaryInfo, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, inputActivities: [(Peer, PeerInputActivity)]?, isAd: Bool, hasFailedMessages: Bool) + case PeerEntry(index: ChatListIndex, presentationData: ChatListPresentationData, message: Message?, readState: CombinedPeerReadState?, notificationSettings: PeerNotificationSettings?, embeddedInterfaceState: PeerChatListEmbeddedInterfaceState?, peer: RenderedPeer, presence: PeerPresence?, summaryInfo: ChatListMessageTagSummaryInfo, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, inputActivities: [(Peer, PeerInputActivity)]?, isAd: Bool, hasFailedMessages: Bool, isContact: Bool) case HoleEntry(ChatListHole, theme: PresentationTheme) case GroupReferenceEntry(index: ChatListIndex, presentationData: ChatListPresentationData, groupId: PeerGroupId, peers: [ChatListGroupReferencePeer], message: Message?, editing: Bool, unreadState: PeerGroupUnreadCountersCombinedSummary, revealed: Bool, hiddenByDefault: Bool) case ArchiveIntro(presentationData: ChatListPresentationData) + case AdditionalCategory(index: Int, id: Int, title: String, image: UIImage?, selected: Bool, presentationData: ChatListPresentationData) - var sortIndex: ChatListIndex { + var sortIndex: ChatListNodeEntrySortIndex { switch self { case .HeaderEntry: - return ChatListIndex.absoluteUpperBound - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _): - return index + return .index(ChatListIndex.absoluteUpperBound) + case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + return .index(index) case let .HoleEntry(hole, _): - return ChatListIndex(pinningIndex: nil, messageIndex: hole.index) + return .index(ChatListIndex(pinningIndex: nil, messageIndex: hole.index)) case let .GroupReferenceEntry(index, _, _, _, _, _, _, _, _): - return index + return .index(index) case .ArchiveIntro: - return ChatListIndex.absoluteUpperBound.successor + return .index(ChatListIndex.absoluteUpperBound.successor) + case let .AdditionalCategory(additionalCategory): + return .additionalCategory(additionalCategory.index) } } @@ -39,7 +68,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { switch self { case .HeaderEntry: return .Header - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return .PeerId(index.messageIndex.id.peerId.toInt64()) case let .HoleEntry(hole, _): return .Hole(Int64(hole.index.id.id)) @@ -47,6 +76,8 @@ enum ChatListNodeEntry: Comparable, Identifiable { return .GroupId(groupId) case .ArchiveIntro: return .ArchiveIntro + case let .AdditionalCategory(additionalCategory): + return .additionalCategory(additionalCategory.id) } } @@ -62,9 +93,9 @@ enum ChatListNodeEntry: Comparable, Identifiable { } else { return false } - case let .PeerEntry(lhsIndex, lhsPresentationData, lhsMessage, lhsUnreadCount, lhsNotificationSettings, lhsEmbeddedState, lhsPeer, lhsPresence, lhsSummaryInfo, lhsEditing, lhsHasRevealControls, lhsSelected, lhsInputActivities, lhsAd, lhsHasFailedMessages): + case let .PeerEntry(lhsIndex, lhsPresentationData, lhsMessage, lhsUnreadCount, lhsNotificationSettings, lhsEmbeddedState, lhsPeer, lhsPresence, lhsSummaryInfo, lhsEditing, lhsHasRevealControls, lhsSelected, lhsInputActivities, lhsAd, lhsHasFailedMessages, lhsIsContact): switch rhs { - case let .PeerEntry(rhsIndex, rhsPresentationData, rhsMessage, rhsUnreadCount, rhsNotificationSettings, rhsEmbeddedState, rhsPeer, rhsPresence, rhsSummaryInfo, rhsEditing, rhsHasRevealControls, rhsSelected, rhsInputActivities, rhsAd, rhsHasFailedMessages): + case let .PeerEntry(rhsIndex, rhsPresentationData, rhsMessage, rhsUnreadCount, rhsNotificationSettings, rhsEmbeddedState, rhsPeer, rhsPresence, rhsSummaryInfo, rhsEditing, rhsHasRevealControls, rhsSelected, rhsInputActivities, rhsAd, rhsHasFailedMessages, rhsIsContact): if lhsIndex != rhsIndex { return false } @@ -148,6 +179,9 @@ enum ChatListNodeEntry: Comparable, Identifiable { if lhsHasFailedMessages != rhsHasFailedMessages { return false } + if lhsIsContact != rhsIsContact { + return false + } return true default: return false @@ -201,6 +235,30 @@ enum ChatListNodeEntry: Comparable, Identifiable { } else { return false } + case let .AdditionalCategory(lhsIndex, lhsId, lhsTitle, lhsImage, lhsSelected, lhsPresentationData): + if case let .AdditionalCategory(rhsIndex, rhsId, rhsTitle, rhsImage, rhsSelected, rhsPresentationData) = rhs { + if lhsIndex != rhsIndex { + return false + } + if lhsId != rhsId { + return false + } + if lhsTitle != rhsTitle { + return false + } + if lhsImage !== rhsImage { + return false + } + if lhsSelected != rhsSelected { + return false + } + if lhsPresentationData !== rhsPresentationData { + return false + } + return true + } else { + return false + } } } } @@ -220,7 +278,7 @@ func chatListNodeEntriesForView(_ view: ChatListView, state: ChatListNodeState, if view.laterIndex == nil, case .chatList = mode { var groupEntryCount = 0 - for groupReference in view.groupEntries { + for _ in view.groupEntries { groupEntryCount += 1 } pinnedIndexOffset += UInt16(groupEntryCount) @@ -231,7 +289,7 @@ func chatListNodeEntriesForView(_ view: ChatListView, state: ChatListNodeState, } loop: for entry in view.entries { switch entry { - case let .MessageEntry(index, message, combinedReadState, notificationSettings, embeddedState, peer, peerPresence, summaryInfo, hasFailed): + case let .MessageEntry(index, message, combinedReadState, notificationSettings, embeddedState, peer, peerPresence, summaryInfo, hasFailed, isContact): if let savedMessagesPeer = savedMessagesPeer, savedMessagesPeer.id == index.messageIndex.id.peerId { continue loop } @@ -244,10 +302,10 @@ func chatListNodeEntriesForView(_ view: ChatListView, state: ChatListNodeState, updatedMessage = nil updatedCombinedReadState = nil } - result.append(.PeerEntry(index: offsetPinnedIndex(index, offset: pinnedIndexOffset), presentationData: state.presentationData, message: updatedMessage, readState: updatedCombinedReadState, notificationSettings: notificationSettings, embeddedInterfaceState: embeddedState, peer: peer, presence: peerPresence, summaryInfo: summaryInfo, editing: state.editing, hasActiveRevealControls: index.messageIndex.id.peerId == state.peerIdWithRevealedOptions, selected: state.selectedPeerIds.contains(index.messageIndex.id.peerId), inputActivities: state.peerInputActivities?.activities[index.messageIndex.id.peerId], isAd: false, hasFailedMessages: hasFailed)) + result.append(.PeerEntry(index: offsetPinnedIndex(index, offset: pinnedIndexOffset), presentationData: state.presentationData, message: updatedMessage, readState: updatedCombinedReadState, notificationSettings: notificationSettings, embeddedInterfaceState: embeddedState, peer: peer, presence: peerPresence, summaryInfo: summaryInfo, editing: state.editing, hasActiveRevealControls: index.messageIndex.id.peerId == state.peerIdWithRevealedOptions, selected: state.selectedPeerIds.contains(index.messageIndex.id.peerId), inputActivities: state.peerInputActivities?.activities[index.messageIndex.id.peerId], isAd: false, hasFailedMessages: hasFailed, isContact: isContact)) case let .HoleEntry(hole): if hole.index.timestamp == Int32.max - 1 { - return ([], true) + return ([.HeaderEntry], true) } result.append(.HoleEntry(hole, theme: state.presentationData.theme)) } @@ -256,13 +314,13 @@ func chatListNodeEntriesForView(_ view: ChatListView, state: ChatListNodeState, var pinningIndex: UInt16 = UInt16(pinnedIndexOffset == 0 ? 0 : (pinnedIndexOffset - 1)) if let savedMessagesPeer = savedMessagesPeer { - result.append(.PeerEntry(index: ChatListIndex.absoluteUpperBound.predecessor, presentationData: state.presentationData, message: nil, readState: nil, notificationSettings: nil, embeddedInterfaceState: nil, peer: RenderedPeer(peerId: savedMessagesPeer.id, peers: SimpleDictionary([savedMessagesPeer.id: savedMessagesPeer])), presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), editing: state.editing, hasActiveRevealControls: false, selected: false, inputActivities: nil, isAd: false, hasFailedMessages: false)) + result.append(.PeerEntry(index: ChatListIndex.absoluteUpperBound.predecessor, presentationData: state.presentationData, message: nil, readState: nil, notificationSettings: nil, embeddedInterfaceState: nil, peer: RenderedPeer(peerId: savedMessagesPeer.id, peers: SimpleDictionary([savedMessagesPeer.id: savedMessagesPeer])), presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), editing: state.editing, hasActiveRevealControls: false, selected: false, inputActivities: nil, isAd: false, hasFailedMessages: false, isContact: false)) } else { if !view.additionalItemEntries.isEmpty { for entry in view.additionalItemEntries.reversed() { switch entry { - case let .MessageEntry(index, message, combinedReadState, notificationSettings, embeddedState, peer, peerPresence, summaryInfo, hasFailed): - result.append(.PeerEntry(index: ChatListIndex(pinningIndex: pinningIndex, messageIndex: index.messageIndex), presentationData: state.presentationData, message: message, readState: combinedReadState, notificationSettings: notificationSettings, embeddedInterfaceState: embeddedState, peer: peer, presence: peerPresence, summaryInfo: summaryInfo, editing: state.editing, hasActiveRevealControls: index.messageIndex.id.peerId == state.peerIdWithRevealedOptions, selected: state.selectedPeerIds.contains(index.messageIndex.id.peerId), inputActivities: state.peerInputActivities?.activities[index.messageIndex.id.peerId], isAd: true, hasFailedMessages: hasFailed)) + case let .MessageEntry(index, message, combinedReadState, notificationSettings, embeddedState, peer, peerPresence, summaryInfo, hasFailed, isContact): + result.append(.PeerEntry(index: ChatListIndex(pinningIndex: pinningIndex, messageIndex: index.messageIndex), presentationData: state.presentationData, message: message, readState: combinedReadState, notificationSettings: notificationSettings, embeddedInterfaceState: embeddedState, peer: peer, presence: peerPresence, summaryInfo: summaryInfo, editing: state.editing, hasActiveRevealControls: index.messageIndex.id.peerId == state.peerIdWithRevealedOptions, selected: state.selectedPeerIds.contains(index.messageIndex.id.peerId), inputActivities: state.peerInputActivities?.activities[index.messageIndex.id.peerId], isAd: true, hasFailedMessages: hasFailed, isContact: isContact)) if pinningIndex != 0 { pinningIndex -= 1 } @@ -288,12 +346,20 @@ func chatListNodeEntriesForView(_ view: ChatListView, state: ChatListNodeState, result.append(.HeaderEntry) } + + if view.laterIndex == nil, case let .peers(_, _, additionalCategories) = mode { + var index = 0 + for category in additionalCategories.reversed(){ + result.append(.AdditionalCategory(index: index, id: category.id, title: category.title, image: category.icon, selected: state.selectedAdditionalCategoryIds.contains(category.id), presentationData: state.presentationData)) + index += 1 + } + } } if result.count >= 1, case .HoleEntry = result[result.count - 1] { - return ([], true) + return ([.HeaderEntry], true) } else if result.count == 1, case .HoleEntry = result[0] { - return ([], true) + return ([.HeaderEntry], true) } return (result, false) } diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift index 802010adb0..a5ac5daeae 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift @@ -29,9 +29,14 @@ struct ChatListNodeViewUpdate { let scrollPosition: ChatListNodeViewScrollPosition? } -func chatListFilterPredicate(filter: ChatListFilter) -> ChatListFilterPredicate { +func chatListFilterPredicate(filter: ChatListFilterData) -> ChatListFilterPredicate { let includePeers = Set(filter.includePeers) - return ChatListFilterPredicate(includePeerIds: includePeers, include: { peer, notificationSettings, isUnread in + let excludePeers = Set(filter.excludePeers) + var includeAdditionalPeerGroupIds: [PeerGroupId] = [] + if !filter.excludeArchived { + includeAdditionalPeerGroupIds.append(Namespaces.PeerGroup.archive) + } + return ChatListFilterPredicate(includePeerIds: includePeers, excludePeerIds: excludePeers, includeAdditionalPeerGroupIds: includeAdditionalPeerGroupIds, include: { peer, notificationSettings, isUnread, isContact in if filter.excludeRead { if !isUnread { return false @@ -46,15 +51,21 @@ func chatListFilterPredicate(filter: ChatListFilter) -> ChatListFilterPredicate return false } } - if !filter.categories.contains(.privateChats) { + if !filter.categories.contains(.contacts) && isContact { if let user = peer as? TelegramUser { if user.botInfo == nil { return false } + } else if let _ = peer as? TelegramSecretChat { + return false } } - if !filter.categories.contains(.secretChats) { - if let _ = peer as? TelegramSecretChat { + if !filter.categories.contains(.nonContacts) && !isContact { + if let user = peer as? TelegramUser { + if user.botInfo == nil { + return false + } + } else if let _ = peer as? TelegramSecretChat { return false } } @@ -65,23 +76,12 @@ func chatListFilterPredicate(filter: ChatListFilter) -> ChatListFilterPredicate } } } - if !filter.categories.contains(.privateGroups) { + if !filter.categories.contains(.groups) { if let _ = peer as? TelegramGroup { return false } else if let channel = peer as? TelegramChannel { if case .group = channel.info { - if channel.username == nil { - return false - } - } - } - } - if !filter.categories.contains(.publicGroups) { - if let channel = peer as? TelegramChannel { - if case .group = channel.info { - if channel.username != nil { - return false - } + return false } } } @@ -97,7 +97,7 @@ func chatListFilterPredicate(filter: ChatListFilter) -> ChatListFilterPredicate } func chatListViewForLocation(groupId: PeerGroupId, location: ChatListNodeLocation, account: Account) -> Signal { - let filterPredicate: ChatListFilterPredicate? = location.filter.flatMap(chatListFilterPredicate) + let filterPredicate: ChatListFilterPredicate? = (location.filter?.data).flatMap(chatListFilterPredicate) switch location { case let .initial(count, _): diff --git a/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift b/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift index ae320a81e9..ce0208db95 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift @@ -45,6 +45,7 @@ struct ChatListNodeViewTransition { let scrollToItem: ListViewScrollToItem? let stationaryItemRange: (Int, Int)? let adjustScrollToFirstItem: Bool + let animateCrossfade: Bool } enum ChatListNodeViewScrollPosition { @@ -71,7 +72,6 @@ func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toV var options: ListViewDeleteAndInsertOptions = [] var maxAnimatedInsertionIndex = -1 - var stationaryItemRange: (Int, Int)? var scrollToItem: ListViewScrollToItem? switch reason { @@ -89,8 +89,8 @@ func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toV var minTimestamp: Int32? var maxTimestamp: Int32? for (_, item, _) in indicesAndItems { - if case .PeerEntry = item, item.sortIndex.pinningIndex == nil { - let timestamp = item.sortIndex.messageIndex.timestamp + if case .PeerEntry = item, case let .index(index) = item.sortIndex, index.pinningIndex == nil { + let timestamp = index.messageIndex.timestamp if minTimestamp == nil || timestamp < minTimestamp! { minTimestamp = timestamp @@ -146,7 +146,7 @@ func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toV case let .index(scrollIndex, position, directionHint, animated): var index = toView.filteredEntries.count - 1 for entry in toView.filteredEntries { - if entry.sortIndex >= scrollIndex { + if entry.sortIndex >= .index(scrollIndex) { scrollToItem = ListViewScrollToItem(index: index, position: position, animated: animated, curve: .Default(duration: nil), directionHint: directionHint) break } @@ -156,7 +156,7 @@ func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toV if scrollToItem == nil { var index = 0 for entry in toView.filteredEntries.reversed() { - if entry.sortIndex < scrollIndex { + if entry.sortIndex < .index(scrollIndex) { scrollToItem = ListViewScrollToItem(index: index, position: position, animated: animated, curve: .Default(duration: nil), directionHint: directionHint) break } @@ -167,8 +167,27 @@ func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toV } var fromEmptyView = false + var animateCrossfade = false if let fromView = fromView { - if fromView.filteredEntries.isEmpty || fromView.filter != toView.filter { + var wasSingleHeader = false + if fromView.filteredEntries.count == 1, case .HeaderEntry = fromView.filteredEntries[0] { + wasSingleHeader = true + } + var isSingleHeader = false + if toView.filteredEntries.count == 1, case .HeaderEntry = toView.filteredEntries[0] { + isSingleHeader = true + } + if (wasSingleHeader || isSingleHeader), case .interactiveChanges = reason { + if wasSingleHeader != isSingleHeader { + if wasSingleHeader { + animateCrossfade = true + options.remove(.AnimateInsertion) + options.remove(.AnimateAlpha) + } else { + let _ = options.insert(.AnimateInsertion) + } + } + } else if fromView.filteredEntries.isEmpty || fromView.filter != toView.filter { options.remove(.AnimateInsertion) options.remove(.AnimateAlpha) fromEmptyView = true @@ -182,7 +201,7 @@ func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toV adjustScrollToFirstItem = true } - subscriber.putNext(ChatListNodeViewTransition(chatListView: toView, deleteItems: adjustedDeleteIndices, insertEntries: adjustedIndicesAndItems, updateEntries: adjustedUpdateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: stationaryItemRange, adjustScrollToFirstItem: adjustScrollToFirstItem)) + subscriber.putNext(ChatListNodeViewTransition(chatListView: toView, deleteItems: adjustedDeleteIndices, insertEntries: adjustedIndicesAndItems, updateEntries: adjustedUpdateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: nil, adjustScrollToFirstItem: adjustScrollToFirstItem, animateCrossfade: animateCrossfade)) subscriber.putCompletion() return EmptyDisposable diff --git a/submodules/ChatListUI/Sources/TabBarChatListFilterController.swift b/submodules/ChatListUI/Sources/TabBarChatListFilterController.swift index c02d9656b6..2af6a41583 100644 --- a/submodules/ChatListUI/Sources/TabBarChatListFilterController.swift +++ b/submodules/ChatListUI/Sources/TabBarChatListFilterController.swift @@ -10,317 +10,15 @@ import Postbox import TelegramUIPreferences import TelegramCore -final class TabBarChatListFilterController: ViewController { - private var controllerNode: TabBarChatListFilterControllerNode { - return self.displayNode as! TabBarChatListFilterControllerNode - } - - private let _ready = Promise() - override public var ready: Promise { - return self._ready - } - - private let context: AccountContext - private let sourceNodes: [ASDisplayNode] - private let presetList: [ChatListFilter] - private let currentPreset: ChatListFilter? - private let setup: () -> Void - private let updatePreset: (ChatListFilter?) -> Void - - private var presentationData: PresentationData - private var didPlayPresentationAnimation = false - - private let hapticFeedback = HapticFeedback() - - public init(context: AccountContext, sourceNodes: [ASDisplayNode], presetList: [ChatListFilter], currentPreset: ChatListFilter?, setup: @escaping () -> Void, updatePreset: @escaping (ChatListFilter?) -> Void) { - self.context = context - self.sourceNodes = sourceNodes - self.presetList = presetList - self.currentPreset = currentPreset - self.setup = setup - self.updatePreset = updatePreset - - self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - - super.init(navigationBarPresentationData: nil) - - self.statusBar.statusBarStyle = .Ignore - self.statusBar.ignoreInCall = true - - self.lockOrientation = true - } - - required public init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - } - - override public func loadDisplayNode() { - self.displayNode = TabBarChatListFilterControllerNode(context: self.context, presentationData: self.presentationData, cancel: { [weak self] in - self?.dismiss() - }, sourceNodes: self.sourceNodes, presetList: self.presetList, currentPreset: self.currentPreset, setup: { [weak self] in - self?.setup() - self?.dismiss(sourceNodes: [], fadeOutIcon: true) - }, updatePreset: { [weak self] filter in - self?.updatePreset(filter) - self?.dismiss() - }) - self._ready.set(self.controllerNode.isReady.get()) - self.displayNodeDidLoad() - } - - override public func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - if !self.didPlayPresentationAnimation { - self.didPlayPresentationAnimation = true - - self.hapticFeedback.impact() - self.controllerNode.animateIn() - } - } - - override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - super.containerLayoutUpdated(layout, transition: transition) - - self.controllerNode.containerLayoutUpdated(layout, transition: transition) - } - - override public func dismiss(completion: (() -> Void)? = nil) { - self.dismiss(sourceNodes: [], fadeOutIcon: false) - } - - func dismiss(sourceNodes: [ASDisplayNode], fadeOutIcon: Bool) { - self.controllerNode.animateOut(sourceNodes: sourceNodes, fadeOutIcon: fadeOutIcon, completion: { [weak self] in - self?.didPlayPresentationAnimation = false - self?.presentingViewController?.dismiss(animated: false, completion: nil) - }) - } -} - -private let animationDurationFactor: Double = 1.0 - -private protocol AbstractTabBarChatListFilterItemNode { - func updateLayout(maxWidth: CGFloat) -> (CGFloat, CGFloat, (CGFloat) -> Void) -} - -private final class AddFilterItemNode: ASDisplayNode, AbstractTabBarChatListFilterItemNode { - private let action: () -> Void - - private let separatorNode: ASDisplayNode - private let highlightedBackgroundNode: ASDisplayNode - private let buttonNode: HighlightTrackingButtonNode - private let plusNode: ASImageNode - private let titleNode: ImmediateTextNode - - init(displaySeparator: Bool, presentationData: PresentationData, action: @escaping () -> Void) { - self.action = action - - self.separatorNode = ASDisplayNode() - self.separatorNode.backgroundColor = presentationData.theme.actionSheet.opaqueItemSeparatorColor - self.separatorNode.isHidden = !displaySeparator - - self.highlightedBackgroundNode = ASDisplayNode() - self.highlightedBackgroundNode.backgroundColor = presentationData.theme.actionSheet.opaqueItemHighlightedBackgroundColor - self.highlightedBackgroundNode.alpha = 0.0 - - self.buttonNode = HighlightTrackingButtonNode() - - self.titleNode = ImmediateTextNode() - self.titleNode.maximumNumberOfLines = 1 - self.titleNode.attributedText = NSAttributedString(string: "Setup", font: Font.regular(17.0), textColor: presentationData.theme.actionSheet.primaryTextColor) - - self.plusNode = ASImageNode() - self.plusNode.image = generateItemListPlusIcon(presentationData.theme.actionSheet.primaryTextColor) - - super.init() - - self.addSubnode(self.separatorNode) - self.addSubnode(self.highlightedBackgroundNode) - self.addSubnode(self.titleNode) - self.addSubnode(self.plusNode) - self.addSubnode(self.buttonNode) - - self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) - self.buttonNode.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - strongSelf.highlightedBackgroundNode.layer.removeAnimation(forKey: "opacity") - strongSelf.highlightedBackgroundNode.alpha = 1.0 - } else { - strongSelf.highlightedBackgroundNode.alpha = 0.0 - strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) - } - } - } - } - - func updateLayout(maxWidth: CGFloat) -> (CGFloat, CGFloat, (CGFloat) -> Void) { - let leftInset: CGFloat = 16.0 - let rightInset: CGFloat = 10.0 - let iconInset: CGFloat = 60.0 - let titleSize = self.titleNode.updateLayout(CGSize(width: maxWidth - leftInset - rightInset, height: .greatestFiniteMagnitude)) - let height: CGFloat = 61.0 - - return (titleSize.width + leftInset + rightInset, height, { width in - self.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - titleSize.height) / 2.0)), size: titleSize) - - if let image = self.plusNode.image { - self.plusNode.frame = CGRect(origin: CGPoint(x: floor(width - iconInset + (iconInset - image.size.width) / 2.0), y: floor((height - image.size.height) / 2.0)), size: image.size) - } - - self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: height - UIScreenPixel), size: CGSize(width: width, height: UIScreenPixel)) - self.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: height)) - self.buttonNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: height)) - }) - } - - @objc private func buttonPressed() { - self.action() - } -} - -private final class FilterItemNode: ASDisplayNode, AbstractTabBarChatListFilterItemNode { - private let context: AccountContext - private let title: String - let preset: ChatListFilter? - private let isCurrent: Bool - private let presentationData: PresentationData - private let action: () -> Bool - - private let separatorNode: ASDisplayNode - private let highlightedBackgroundNode: ASDisplayNode - private let buttonNode: HighlightTrackingButtonNode - private let titleNode: ImmediateTextNode - private let checkNode: ASImageNode - - private let badgeBackgroundNode: ASImageNode - private let badgeTitleNode: ImmediateTextNode - private var badgeText: String = "" - - init(context: AccountContext, title: String, preset: ChatListFilter?, isCurrent: Bool, displaySeparator: Bool, presentationData: PresentationData, action: @escaping () -> Bool) { - self.context = context - self.title = title - self.preset = preset - self.isCurrent = isCurrent - self.presentationData = presentationData - self.action = action - - self.separatorNode = ASDisplayNode() - self.separatorNode.backgroundColor = presentationData.theme.actionSheet.opaqueItemSeparatorColor - self.separatorNode.isHidden = !displaySeparator - - self.highlightedBackgroundNode = ASDisplayNode() - self.highlightedBackgroundNode.backgroundColor = presentationData.theme.actionSheet.opaqueItemHighlightedBackgroundColor - self.highlightedBackgroundNode.alpha = 0.0 - - self.buttonNode = HighlightTrackingButtonNode() - - self.titleNode = ImmediateTextNode() - self.titleNode.maximumNumberOfLines = 1 - self.titleNode.attributedText = NSAttributedString(string: title, font: Font.regular(17.0), textColor: presentationData.theme.actionSheet.primaryTextColor) - - self.checkNode = ASImageNode() - self.checkNode.image = generateItemListCheckIcon(color: presentationData.theme.actionSheet.primaryTextColor) - self.checkNode.isHidden = true//!isCurrent - - self.badgeBackgroundNode = ASImageNode() - self.badgeBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 20.0, color: presentationData.theme.list.itemCheckColors.fillColor) - self.badgeTitleNode = ImmediateTextNode() - self.badgeBackgroundNode.isHidden = true - self.badgeTitleNode.isHidden = true - - super.init() - - self.addSubnode(self.separatorNode) - self.addSubnode(self.highlightedBackgroundNode) - self.addSubnode(self.titleNode) - self.addSubnode(self.checkNode) - self.addSubnode(self.badgeBackgroundNode) - self.addSubnode(self.badgeTitleNode) - self.addSubnode(self.buttonNode) - - self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) - self.buttonNode.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - strongSelf.highlightedBackgroundNode.layer.removeAnimation(forKey: "opacity") - strongSelf.highlightedBackgroundNode.alpha = 1.0 - } else { - strongSelf.highlightedBackgroundNode.alpha = 0.0 - strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) - } - } - } - } - - func updateLayout(maxWidth: CGFloat) -> (CGFloat, CGFloat, (CGFloat) -> Void) { - let leftInset: CGFloat = 16.0 - - let badgeTitleSize = self.badgeTitleNode.updateLayout(CGSize(width: 100.0, height: .greatestFiniteMagnitude)) - let badgeMinSize = self.badgeBackgroundNode.image?.size.width ?? 20.0 - let badgeSize = CGSize(width: max(badgeMinSize, badgeTitleSize.width + 12.0), height: badgeMinSize) - - let rightInset: CGFloat = max(20.0, badgeSize.width + 20.0) - - let titleSize = self.titleNode.updateLayout(CGSize(width: maxWidth - leftInset - rightInset, height: .greatestFiniteMagnitude)) - - let height: CGFloat = 61.0 - - return (titleSize.width + leftInset + rightInset, height, { width in - self.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - titleSize.height) / 2.0)), size: titleSize) - - if let image = self.checkNode.image { - self.checkNode.frame = CGRect(origin: CGPoint(x: width - rightInset + floor((rightInset - image.size.width) / 2.0), y: floor((height - image.size.height) / 2.0)), size: image.size) - } - - let badgeBackgroundFrame = CGRect(origin: CGPoint(x: width - rightInset + floor((rightInset - badgeSize.width) / 2.0), y: floor((height - badgeSize.height) / 2.0)), size: badgeSize) - self.badgeBackgroundNode.frame = badgeBackgroundFrame - self.badgeTitleNode.frame = CGRect(origin: CGPoint(x: badgeBackgroundFrame.minX + floor((badgeBackgroundFrame.width - badgeTitleSize.width) / 2.0), y: badgeBackgroundFrame.minY + floor((badgeBackgroundFrame.height - badgeTitleSize.height) / 2.0)), size: badgeTitleSize) - - self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: height - UIScreenPixel), size: CGSize(width: width, height: UIScreenPixel)) - self.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: height)) - self.buttonNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: height)) - }) - } - - @objc private func buttonPressed() { - let _ = self.action() - //self.checkNode.isHidden = !isCurrent - } - - func updateBadge(text: String) -> Bool { - if text != self.badgeText { - self.badgeText = text - self.badgeTitleNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: self.presentationData.theme.list.itemCheckColors.foregroundColor) - self.badgeBackgroundNode.isHidden = text.isEmpty - self.badgeTitleNode.isHidden = text.isEmpty - return true - } else { - return false - } - } -} - func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilter, Int)]), NoError> { - let preferencesKey: PostboxViewKey = .preferences(keys: [PreferencesKeys.chatListFilters]) - return context.account.postbox.combinedView(keys: [preferencesKey]) - |> map { combinedView -> [ChatListFilter] in - if let filtersState = (combinedView.views[preferencesKey] as? PreferencesView)?.values[PreferencesKeys.chatListFilters] as? ChatListFiltersState { - return filtersState.filters - } else { - return [] - } - } + return updatedChatListFilters(postbox: context.account.postbox) |> distinctUntilChanged |> mapToSignal { filters -> Signal<(Int, [(ChatListFilter, Int)]), NoError> in var unreadCountItems: [UnreadMessageCountsItem] = [] unreadCountItems.append(.total(nil)) var additionalPeerIds = Set() for filter in filters { - additionalPeerIds.formUnion(filter.includePeers) + additionalPeerIds.formUnion(filter.data.includePeers) } if !additionalPeerIds.isEmpty { for peerId in additionalPeerIds { @@ -336,25 +34,13 @@ func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilt return combineLatest(queue: context.account.postbox.queue, context.account.postbox.combinedView(keys: keys), - context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.inAppNotificationSettings]) + Signal.single(true) ) - |> map { view, sharedData -> (Int, [(ChatListFilter, Int)]) in + |> map { view, _ -> (Int, [(ChatListFilter, Int)]) in guard let unreadCounts = view.views[unreadKey] as? UnreadMessageCountsView else { return (0, []) } - let inAppSettings: InAppNotificationSettings - if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.inAppNotificationSettings] as? InAppNotificationSettings { - inAppSettings = value - } else { - inAppSettings = .defaultSettings - } - let type: RenderedTotalUnreadCountType - switch inAppSettings.totalUnreadCountDisplayStyle { - case .filtered: - type = .filtered - } - var result: [(ChatListFilter, Int)] = [] var peerTagAndCount: [PeerId: (PeerSummaryCounterTags, Int)] = [:] @@ -364,10 +50,12 @@ func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilt switch entry { case let .total(_, totalStateValue): totalState = totalStateValue + case let .totalInGroup(groupId, totalGroupState): + break case let .peer(peerId, state): if let state = state, state.isUnread { if let peerView = view.views[.basicPeer(peerId)] as? BasicPeerView, let peer = peerView.peer { - let tag = context.account.postbox.seedConfiguration.peerSummaryCounterTags(peer) + let tag = context.account.postbox.seedConfiguration.peerSummaryCounterTags(peer, peerView.isContact) if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings, case .muted = notificationSettings.muteState { peerTagAndCount[peerId] = (tag, 0) } else { @@ -382,30 +70,23 @@ func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilt } } - var totalBadge = 0 - if let totalState = totalState { - totalBadge = Int(totalState.count(for: inAppSettings.totalUnreadCountDisplayStyle.category, in: inAppSettings.totalUnreadCountDisplayCategory.statsType, with: inAppSettings.totalUnreadCountIncludeTags)) - } + let totalBadge = 0 - var shouldUpdateLayout = false for filter in filters { var tags: [PeerSummaryCounterTags] = [] - if filter.categories.contains(.privateChats) { - tags.append(.privateChat) + if filter.data.categories.contains(.contacts) { + tags.append(.contact) } - if filter.categories.contains(.secretChats) { - tags.append(.secretChat) + if filter.data.categories.contains(.nonContacts) { + tags.append(.nonContact) } - if filter.categories.contains(.privateGroups) { - tags.append(.privateGroup) + if filter.data.categories.contains(.groups) { + tags.append(.group) } - if filter.categories.contains(.bots) { + if filter.data.categories.contains(.bots) { tags.append(.bot) } - if filter.categories.contains(.publicGroups) { - tags.append(.publicGroup) - } - if filter.categories.contains(.channels) { + if filter.data.categories.contains(.channels) { tags.append(.channel) } @@ -417,7 +98,7 @@ func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilt } } } - for peerId in filter.includePeers { + for peerId in filter.data.includePeers { if let (tag, peerCount) = peerTagAndCount[peerId] { if !tags.contains(tag) { if peerCount != 0 { @@ -433,399 +114,3 @@ func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilt } } } - -private final class TabBarChatListFilterControllerNode: ViewControllerTracingNode { - private let presentationData: PresentationData - private let cancel: () -> Void - - private let effectView: UIVisualEffectView - private var propertyAnimator: AnyObject? - private var displayLinkAnimator: DisplayLinkAnimator? - private let dimNode: ASDisplayNode - - private let contentContainerNode: ASDisplayNode - private let contentNodes: [ASDisplayNode & AbstractTabBarChatListFilterItemNode] - - private var sourceNodes: [ASDisplayNode] - private var snapshotViews: [UIView] = [] - - private var validLayout: ContainerViewLayout? - - private var countsDisposable: Disposable? - let isReady = Promise() - private var didSetIsReady = false - - init(context: AccountContext, presentationData: PresentationData, cancel: @escaping () -> Void, sourceNodes: [ASDisplayNode], presetList: [ChatListFilter], currentPreset: ChatListFilter?, setup: @escaping () -> Void, updatePreset: @escaping (ChatListFilter?) -> Void) { - self.presentationData = presentationData - self.cancel = cancel - self.sourceNodes = sourceNodes - - self.effectView = UIVisualEffectView() - if #available(iOS 9.0, *) { - } else { - if presentationData.theme.rootController.keyboardColor == .dark { - self.effectView.effect = UIBlurEffect(style: .dark) - } else { - self.effectView.effect = UIBlurEffect(style: .light) - } - self.effectView.alpha = 0.0 - } - - self.dimNode = ASDisplayNode() - self.dimNode.alpha = 1.0 - if presentationData.theme.rootController.keyboardColor == .light { - self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.04) - } else { - self.dimNode.backgroundColor = presentationData.theme.chatList.backgroundColor.withAlphaComponent(0.2) - } - - self.contentContainerNode = ASDisplayNode() - self.contentContainerNode.backgroundColor = self.presentationData.theme.actionSheet.opaqueItemBackgroundColor - self.contentContainerNode.cornerRadius = 20.0 - self.contentContainerNode.clipsToBounds = true - - var contentNodes: [ASDisplayNode & AbstractTabBarChatListFilterItemNode] = [] - contentNodes.append(AddFilterItemNode(displaySeparator: true, presentationData: presentationData, action: { - setup() - })) - - for i in 0 ..< presetList.count { - let preset = presetList[i] - - let title: String = preset.title ?? "" - contentNodes.append(FilterItemNode(context: context, title: title, preset: preset, isCurrent: currentPreset == preset, displaySeparator: i != presetList.count - 1, presentationData: presentationData, action: { - updatePreset(preset) - return false - })) - } - self.contentNodes = contentNodes - - super.init() - - self.view.addSubview(self.effectView) - self.addSubnode(self.dimNode) - self.addSubnode(self.contentContainerNode) - self.contentNodes.forEach(self.contentContainerNode.addSubnode) - - self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) - - var unreadCountItems: [UnreadMessageCountsItem] = [] - unreadCountItems.append(.total(nil)) - var additionalPeerIds = Set() - for preset in presetList { - additionalPeerIds.formUnion(preset.includePeers) - } - if !additionalPeerIds.isEmpty { - for peerId in additionalPeerIds { - unreadCountItems.append(.peer(peerId)) - } - } - let unreadKey: PostboxViewKey = .unreadCounts(items: unreadCountItems) - var keys: [PostboxViewKey] = [] - keys.append(unreadKey) - for peerId in additionalPeerIds { - keys.append(.basicPeer(peerId)) - } - - self.countsDisposable = (context.account.postbox.combinedView(keys: keys) - |> deliverOnMainQueue).start(next: { [weak self] view in - guard let strongSelf = self else { - return - } - - if let unreadCounts = view.views[unreadKey] as? UnreadMessageCountsView { - var peerTagAndCount: [PeerId: (PeerSummaryCounterTags, Int)] = [:] - - var totalState: ChatListTotalUnreadState? - for entry in unreadCounts.entries { - switch entry { - case let .total(_, totalStateValue): - totalState = totalStateValue - case let .peer(peerId, state): - if let state = state, state.isUnread { - if let peerView = view.views[.basicPeer(peerId)] as? BasicPeerView, let peer = peerView.peer { - let tag = context.account.postbox.seedConfiguration.peerSummaryCounterTags(peer) - var peerCount = Int(state.count) - if state.isUnread { - peerCount = max(1, peerCount) - } - peerTagAndCount[peerId] = (tag, peerCount) - } - } - } - } - - var totalUnreadChatCount = 0 - if let totalState = totalState { - for (_, counters) in totalState.filteredCounters { - totalUnreadChatCount += Int(counters.chatCount) - } - } - - var shouldUpdateLayout = false - for case let contentNode as FilterItemNode in strongSelf.contentNodes { - let badgeString: String - if let preset = contentNode.preset { - var tags: [PeerSummaryCounterTags] = [] - if preset.categories.contains(.privateChats) { - tags.append(.privateChat) - } - if preset.categories.contains(.secretChats) { - tags.append(.secretChat) - } - if preset.categories.contains(.privateGroups) { - tags.append(.privateGroup) - } - if preset.categories.contains(.bots) { - tags.append(.bot) - } - if preset.categories.contains(.publicGroups) { - tags.append(.publicGroup) - } - if preset.categories.contains(.channels) { - tags.append(.channel) - } - - var count = 0 - if let totalState = totalState { - for tag in tags { - if let value = totalState.filteredCounters[tag] { - count += Int(value.chatCount) - } - } - } - for peerId in preset.includePeers { - if let (tag, peerCount) = peerTagAndCount[peerId] { - if !tags.contains(tag) { - count += peerCount - } - } - } - if count != 0 { - badgeString = "\(count)" - } else { - badgeString = "" - } - } else { - badgeString = "" - } - if contentNode.updateBadge(text: badgeString) { - shouldUpdateLayout = true - } - } - - if shouldUpdateLayout { - if let layout = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout, transition: .immediate) - } - } - } - - if !strongSelf.didSetIsReady { - strongSelf.didSetIsReady = true - strongSelf.isReady.set(.single(true)) - } - }) - } - - deinit { - if let propertyAnimator = self.propertyAnimator { - if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { - let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator - propertyAnimator?.stopAnimation(true) - } - } - - self.countsDisposable?.dispose() - } - - func animateIn() { - self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - - if #available(iOS 10.0, *) { - if let propertyAnimator = self.propertyAnimator { - let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator - propertyAnimator?.stopAnimation(true) - } - self.propertyAnimator = UIViewPropertyAnimator(duration: 0.2 * animationDurationFactor, curve: .easeInOut, animations: { [weak self] in - self?.effectView.effect = makeCustomZoomBlurEffect() - }) - } - - if let _ = self.propertyAnimator { - if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { - self.displayLinkAnimator = DisplayLinkAnimator(duration: 0.2 * animationDurationFactor, from: 0.0, to: 1.0, update: { [weak self] value in - (self?.propertyAnimator as? UIViewPropertyAnimator)?.fractionComplete = value - }, completion: { - }) - } - } else { - UIView.animate(withDuration: 0.2 * animationDurationFactor, animations: { - self.effectView.effect = makeCustomZoomBlurEffect() - }, completion: { _ in - }) - } - - self.contentContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - - if let _ = self.validLayout, let sourceNode = self.sourceNodes.first { - let sourceFrame = sourceNode.view.convert(sourceNode.bounds, to: self.view) - self.contentContainerNode.layer.animateFrame(from: sourceFrame, to: self.contentContainerNode.frame, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) - } - - for sourceNode in self.sourceNodes { - if let imageNode = sourceNode as? ASImageNode { - let snapshot = UIImageView() - snapshot.image = imageNode.image - snapshot.frame = sourceNode.view.convert(sourceNode.bounds, to: self.view) - snapshot.isUserInteractionEnabled = false - self.view.addSubview(snapshot) - self.snapshotViews.append(snapshot) - } else if let snapshot = sourceNode.view.snapshotContentTree() { - snapshot.frame = sourceNode.view.convert(sourceNode.bounds, to: self.view) - snapshot.isUserInteractionEnabled = false - self.view.addSubview(snapshot) - self.snapshotViews.append(snapshot) - } - sourceNode.alpha = 0.0 - } - } - - func animateOut(sourceNodes: [ASDisplayNode], fadeOutIcon: Bool, completion: @escaping () -> Void) { - self.isUserInteractionEnabled = false - - var completedEffect = false - var completedSourceNodes = false - - let intermediateCompletion: () -> Void = { - if completedEffect && completedSourceNodes { - completion() - } - } - - if #available(iOS 10.0, *) { - if let propertyAnimator = self.propertyAnimator { - let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator - propertyAnimator?.stopAnimation(true) - } - self.propertyAnimator = UIViewPropertyAnimator(duration: 0.2, curve: .easeInOut, animations: { [weak self] in - self?.effectView.effect = nil - }) - } - - if let _ = self.propertyAnimator { - if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { - self.displayLinkAnimator = DisplayLinkAnimator(duration: 0.2 * animationDurationFactor, from: 0.0, to: 0.999, update: { [weak self] value in - (self?.propertyAnimator as? UIViewPropertyAnimator)?.fractionComplete = value - }, completion: { [weak self] in - if let strongSelf = self { - for sourceNode in strongSelf.sourceNodes { - sourceNode.alpha = 1.0 - } - } - - completedEffect = true - intermediateCompletion() - }) - } - self.effectView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.05 * animationDurationFactor, delay: 0.15, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false) - } else { - UIView.animate(withDuration: 0.21 * animationDurationFactor, animations: { - if #available(iOS 9.0, *) { - self.effectView.effect = nil - } else { - self.effectView.alpha = 0.0 - } - }, completion: { [weak self] _ in - if let strongSelf = self { - for sourceNode in strongSelf.sourceNodes { - sourceNode.alpha = 1.0 - } - } - - completedEffect = true - intermediateCompletion() - }) - } - - self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) - self.contentContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false, completion: { _ in - }) - if let _ = self.validLayout, let sourceNode = self.sourceNodes.first { - let sourceFrame = sourceNode.view.convert(sourceNode.bounds, to: self.view) - self.contentContainerNode.layer.animateFrame(from: self.contentContainerNode.frame, to: sourceFrame, duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeIn.rawValue, removeOnCompletion: false) - } - if fadeOutIcon { - for snapshotView in self.snapshotViews { - snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) - } - completedSourceNodes = true - } else { - completedSourceNodes = true - } - } - - func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - self.validLayout = layout - - transition.updateFrame(view: self.effectView, frame: CGRect(origin: CGPoint(), size: layout.size)) - transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - - let sideInset: CGFloat = 18.0 - - var contentSize = CGSize() - contentSize.width = min(layout.size.width - 40.0, 260.0) - var applyNodes: [(ASDisplayNode, CGFloat, (CGFloat) -> Void)] = [] - for itemNode in self.contentNodes { - let (width, height, apply) = itemNode.updateLayout(maxWidth: contentSize.width - sideInset * 2.0) - applyNodes.append((itemNode, height, apply)) - contentSize.width = max(contentSize.width, width) - contentSize.height += height - } - - let insets = layout.insets(options: .input) - - let contentOrigin: CGPoint - if let sourceNode = self.sourceNodes.first, let screenFrame = sourceNode.supernode?.convert(sourceNode.frame, to: nil) { - contentOrigin = CGPoint(x: max(16.0, screenFrame.maxX - contentSize.width + 8.0), y: layout.size.height - 66.0 - insets.bottom - contentSize.height) - } else { - contentOrigin = CGPoint(x: max(16.0, layout.size.width - sideInset - contentSize.width), y: layout.size.height - 66.0 - layout.intrinsicInsets.bottom - contentSize.height) - } - - transition.updateFrame(node: self.contentContainerNode, frame: CGRect(origin: contentOrigin, size: contentSize)) - var nextY: CGFloat = 0.0 - for (itemNode, height, apply) in applyNodes { - transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: nextY), size: CGSize(width: contentSize.width, height: height))) - apply(contentSize.width) - nextY += height - } - } - - @objc private func dimTapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state { - self.cancel() - } - } -} - -private func setAnchorPoint(anchorPoint: CGPoint, forView view: UIView) { - var newPoint = CGPoint(x: view.bounds.size.width * anchorPoint.x, - y: view.bounds.size.height * anchorPoint.y) - - - var oldPoint = CGPoint(x: view.bounds.size.width * view.layer.anchorPoint.x, - y: view.bounds.size.height * view.layer.anchorPoint.y) - - newPoint = newPoint.applying(view.transform) - oldPoint = oldPoint.applying(view.transform) - - var position = view.layer.position - position.x -= oldPoint.x - position.x += newPoint.x - - position.y -= oldPoint.y - position.y += newPoint.y - - view.layer.position = position - view.layer.anchorPoint = anchorPoint -} diff --git a/submodules/ChatTitleActivityNode/BUILD b/submodules/ChatTitleActivityNode/BUILD new file mode 100644 index 0000000000..c6d3c8966e --- /dev/null +++ b/submodules/ChatTitleActivityNode/BUILD @@ -0,0 +1,17 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatTitleActivityNode", + module_name = "ChatTitleActivityNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/LegacyComponents:LegacyComponents", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/ChatTitleActivityNode/Info.plist b/submodules/ChatTitleActivityNode/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/ChatTitleActivityNode/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/ChatTitleActivityNode/Sources/ChatTitleActivityNode.h b/submodules/ChatTitleActivityNode/Sources/ChatTitleActivityNode.h deleted file mode 100644 index 0d41a1270b..0000000000 --- a/submodules/ChatTitleActivityNode/Sources/ChatTitleActivityNode.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// ChatTitleActivityNode.h -// ChatTitleActivityNode -// -// Created by Peter on 8/13/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for ChatTitleActivityNode. -FOUNDATION_EXPORT double ChatTitleActivityNodeVersionNumber; - -//! Project version string for ChatTitleActivityNode. -FOUNDATION_EXPORT const unsigned char ChatTitleActivityNodeVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/CheckNode/BUCK b/submodules/CheckNode/BUCK index 138e1ec664..78852663a9 100644 --- a/submodules/CheckNode/BUCK +++ b/submodules/CheckNode/BUCK @@ -4,14 +4,7 @@ static_library( name = "CheckNode", srcs = glob([ "Sources/**/*.swift", - "Sources/*.m", ]), - headers = glob([ - "Sources/*.h", - ], exclude = ["Sources/TelegramPresentationData.h"]), - exported_headers = glob([ - "Sources/*.h", - ], exclude = ["Sources/TelegramPresentationData.h"]), deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit#shared", "//submodules/Display:Display#shared", diff --git a/submodules/CheckNode/BUILD b/submodules/CheckNode/BUILD new file mode 100644 index 0000000000..fb56fea55e --- /dev/null +++ b/submodules/CheckNode/BUILD @@ -0,0 +1,17 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "CheckNode", + module_name = "CheckNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/LegacyComponents:LegacyComponents", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/CheckNode/Info.plist b/submodules/CheckNode/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/CheckNode/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/CheckNode/Sources/CheckNode.h b/submodules/CheckNode/Sources/CheckNode.h deleted file mode 100644 index 20e15c1826..0000000000 --- a/submodules/CheckNode/Sources/CheckNode.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// CheckNode.h -// CheckNode -// -// Created by Peter on 8/1/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for CheckNode. -FOUNDATION_EXPORT double CheckNodeVersionNumber; - -//! Project version string for CheckNode. -FOUNDATION_EXPORT const unsigned char CheckNodeVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/CloudData/BUCK b/submodules/CloudData/BUCK index dbe59042fe..61dc3a640e 100644 --- a/submodules/CloudData/BUCK +++ b/submodules/CloudData/BUCK @@ -9,6 +9,7 @@ static_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared", "//submodules/Postbox:Postbox#shared", "//submodules/MtProtoKit:MtProtoKit#shared", + "//submodules/EncryptionProvider:EncryptionProvider", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/CloudData/BUILD b/submodules/CloudData/BUILD new file mode 100644 index 0000000000..662ebc2ee3 --- /dev/null +++ b/submodules/CloudData/BUILD @@ -0,0 +1,18 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "CloudData", + module_name = "CloudData", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/Postbox:Postbox", + "//submodules/MtProtoKit:MtProtoKit", + "//submodules/EncryptionProvider:EncryptionProvider", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/ComposePollUI/BUILD b/submodules/ComposePollUI/BUILD new file mode 100644 index 0000000000..df9b19b552 --- /dev/null +++ b/submodules/ComposePollUI/BUILD @@ -0,0 +1,24 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ComposePollUI", + module_name = "ComposePollUI", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/SyncCore:SyncCore", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/ItemListUI:ItemListUI", + "//submodules/AccountContext:AccountContext", + "//submodules/AlertUI:AlertUI", + "//submodules/PresentationDataUtils:PresentationDataUtils", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/ComposePollUI/Info.plist b/submodules/ComposePollUI/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/ComposePollUI/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/ComposePollUI/Sources/ComposePollUI.h b/submodules/ComposePollUI/Sources/ComposePollUI.h deleted file mode 100644 index 0bdf9b7117..0000000000 --- a/submodules/ComposePollUI/Sources/ComposePollUI.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// ComposePollUI.h -// ComposePollUI -// -// Created by Peter on 8/10/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for ComposePollUI. -FOUNDATION_EXPORT double ComposePollUIVersionNumber; - -//! Project version string for ComposePollUI. -FOUNDATION_EXPORT const unsigned char ComposePollUIVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/ContactListUI/BUILD b/submodules/ContactListUI/BUILD new file mode 100644 index 0000000000..9222087c53 --- /dev/null +++ b/submodules/ContactListUI/BUILD @@ -0,0 +1,38 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ContactListUI", + module_name = "ContactListUI", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/SyncCore:SyncCore", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/AccountContext:AccountContext", + "//submodules/TelegramUIPreferences:TelegramUIPreferences", + "//submodules/MergeLists:MergeLists", + "//submodules/SearchUI:SearchUI", + "//submodules/ChatListSearchItemHeader:ChatListSearchItemHeader", + "//submodules/ItemListPeerItem:ItemListPeerItem", + "//submodules/ContactsPeerItem:ContactsPeerItem", + "//submodules/ChatListSearchItemNode:ChatListSearchItemNode", + "//submodules/TelegramPermissionsUI:TelegramPermissionsUI", + "//submodules/TelegramNotices:TelegramNotices", + "//submodules/AlertUI:AlertUI", + "//submodules/PresentationDataUtils:PresentationDataUtils", + "//submodules/ShareController:ShareController", + "//submodules/AppBundle:AppBundle", + "//submodules/OverlayStatusController:OverlayStatusController", + "//submodules/PhoneNumberFormat:PhoneNumberFormat", + "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/ContactListUI/Info.plist b/submodules/ContactListUI/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/ContactListUI/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/ContactListUI/Sources/ContactListUI.h b/submodules/ContactListUI/Sources/ContactListUI.h deleted file mode 100644 index 4d1cb36e9b..0000000000 --- a/submodules/ContactListUI/Sources/ContactListUI.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// ContactListUI.h -// ContactListUI -// -// Created by Peter on 8/12/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for ContactListUI. -FOUNDATION_EXPORT double ContactListUIVersionNumber; - -//! Project version string for ContactListUI. -FOUNDATION_EXPORT const unsigned char ContactListUIVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/ContactsPeerItem/BUILD b/submodules/ContactsPeerItem/BUILD new file mode 100644 index 0000000000..1af726ec27 --- /dev/null +++ b/submodules/ContactsPeerItem/BUILD @@ -0,0 +1,32 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ContactsPeerItem", + module_name = "ContactsPeerItem", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/SyncCore:SyncCore", + "//submodules/AccountContext:AccountContext", + "//submodules/AvatarNode:AvatarNode", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/CheckNode:CheckNode", + "//submodules/TelegramStringFormatting:TelegramStringFormatting", + "//submodules/ItemListPeerItem:ItemListPeerItem", + "//submodules/PeerPresenceStatusManager:PeerPresenceStatusManager", + "//submodules/ItemListUI:ItemListUI", + "//submodules/TelegramUIPreferences:TelegramUIPreferences", + "//submodules/ListSectionHeaderNode:ListSectionHeaderNode", + "//submodules/ContextUI:ContextUI", + "//submodules/PresentationDataUtils:PresentationDataUtils", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/ContactsPeerItem/Info.plist b/submodules/ContactsPeerItem/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/ContactsPeerItem/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.h b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.h deleted file mode 100644 index 9addb32e87..0000000000 --- a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// ContactsPeerItem.h -// ContactsPeerItem -// -// Created by Peter on 8/13/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for ContactsPeerItem. -FOUNDATION_EXPORT double ContactsPeerItemVersionNumber; - -//! Project version string for ContactsPeerItem. -FOUNDATION_EXPORT const unsigned char ContactsPeerItemVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift index 183287730b..f8fc1cd46d 100644 --- a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift +++ b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift @@ -456,8 +456,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { if currentItem?.presentationData.theme !== item.presentationData.theme { updatedTheme = item.presentationData.theme } - var leftInset: CGFloat = 65.0 + params.leftInset - let rightInset: CGFloat = 10.0 + params.rightInset + let leftInset: CGFloat = 65.0 + params.leftInset + var rightInset: CGFloat = 10.0 + params.rightInset let updatedSelectionNode: CheckNode? var isSelected = false @@ -465,7 +465,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { case .none: updatedSelectionNode = nil case let .selectable(selected): - leftInset += 28.0 + rightInset += 38.0 isSelected = selected let selectionNode: CheckNode @@ -849,7 +849,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { } updatedSelectionNode.setIsChecked(isSelected, animated: animated) - updatedSelectionNode.frame = CGRect(origin: CGPoint(x: params.leftInset + 6.0, y: floor((nodeLayout.contentSize.height - 32.0) / 2.0)), size: CGSize(width: 32.0, height: 32.0)) + updatedSelectionNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 32.0 - 12.0, y: floor((nodeLayout.contentSize.height - 32.0) / 2.0)), size: CGSize(width: 32.0, height: 32.0)) } else if let selectionNode = strongSelf.selectionNode { selectionNode.removeFromSupernode() strongSelf.selectionNode = nil diff --git a/submodules/ContextUI/BUILD b/submodules/ContextUI/BUILD new file mode 100644 index 0000000000..f8157be861 --- /dev/null +++ b/submodules/ContextUI/BUILD @@ -0,0 +1,21 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ContextUI", + module_name = "ContextUI", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/TextSelectionNode:TextSelectionNode", + "//submodules/ReactionSelectionNode:ReactionSelectionNode", + "//submodules/AppBundle:AppBundle", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/ContextUI/Info.plist b/submodules/ContextUI/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/ContextUI/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index f63658522d..bf3b21f1e1 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -747,7 +747,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi self.withoutBlurDimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false) } - self.actionsContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2 * animationDurationFactor, removeOnCompletion: false, completion: { _ in + self.actionsContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15 * animationDurationFactor, removeOnCompletion: false, completion: { _ in completedActionsNode = true intermediateCompletion() }) @@ -1363,12 +1363,16 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi if let maybeContentNode = self.contentContainerNode.contentNode { switch maybeContentNode { case let .extracted(contentParentNode, _): - let contentPoint = self.view.convert(point, to: contentParentNode.contentNode.view) - if let result = contentParentNode.contentNode.hitTest(contentPoint, with: event) { - if result is TextSelectionNodeView { - return result - } else if contentParentNode.contentRect.contains(contentPoint) { - return contentParentNode.contentNode.view + if case let .extracted(source) = self.source { + if !source.ignoreContentTouches { + let contentPoint = self.view.convert(point, to: contentParentNode.contentNode.view) + if let result = contentParentNode.contentNode.hitTest(contentPoint, with: event) { + if result is TextSelectionNodeView { + return result + } else if contentParentNode.contentRect.contains(contentPoint) { + return contentParentNode.contentNode.view + } + } } } case let .controller(controller): @@ -1429,6 +1433,7 @@ public final class ContextControllerPutBackViewInfo { public protocol ContextExtractedContentSource: class { var keepInPlace: Bool { get } + var ignoreContentTouches: Bool { get } func takeView() -> ContextControllerTakeViewInfo? func putBack() -> ContextControllerPutBackViewInfo? @@ -1487,6 +1492,7 @@ public final class ContextController: ViewController, StandalonePresentableContr super.init(navigationBarPresentationData: nil) self.statusBar.statusBarStyle = .Hide + self.lockOrientation = true } required init(coder aDecoder: NSCoder) { diff --git a/submodules/ContextUI/Sources/ContextUI.h b/submodules/ContextUI/Sources/ContextUI.h deleted file mode 100644 index 8ba61f93ed..0000000000 --- a/submodules/ContextUI/Sources/ContextUI.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// ContextUI.h -// ContextUI -// -// Created by Peter on 8/5/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for ContextUI. -FOUNDATION_EXPORT double ContextUIVersionNumber; - -//! Project version string for ContextUI. -FOUNDATION_EXPORT const unsigned char ContextUIVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/CounterContollerTitleView/BUILD b/submodules/CounterContollerTitleView/BUILD new file mode 100644 index 0000000000..0462915bd5 --- /dev/null +++ b/submodules/CounterContollerTitleView/BUILD @@ -0,0 +1,17 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "CounterContollerTitleView", + module_name = "CounterContollerTitleView", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/TelegramPresentationData:TelegramPresentationData", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/CounterContollerTitleView/Info.plist b/submodules/CounterContollerTitleView/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/CounterContollerTitleView/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/CounterContollerTitleView/Sources/CounterContollerTitleView.h b/submodules/CounterContollerTitleView/Sources/CounterContollerTitleView.h deleted file mode 100644 index c0ddce18a5..0000000000 --- a/submodules/CounterContollerTitleView/Sources/CounterContollerTitleView.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// CounterContollerTitleView.h -// CounterContollerTitleView -// -// Created by Peter on 8/17/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for CounterContollerTitleView. -FOUNDATION_EXPORT double CounterContollerTitleViewVersionNumber; - -//! Project version string for CounterContollerTitleView. -FOUNDATION_EXPORT const unsigned char CounterContollerTitleViewVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/CountrySelectionUI/BUILD b/submodules/CountrySelectionUI/BUILD new file mode 100644 index 0000000000..7fcc859b8b --- /dev/null +++ b/submodules/CountrySelectionUI/BUILD @@ -0,0 +1,21 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "CountrySelectionUI", + module_name = "CountrySelectionUI", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/TelegramStringFormatting:TelegramStringFormatting", + "//submodules/SearchBarNode:SearchBarNode", + "//submodules/AppBundle:AppBundle", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/CountrySelectionUI/Info.plist b/submodules/CountrySelectionUI/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/CountrySelectionUI/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/CountrySelectionUI/Sources/CountrySelectionUI.h b/submodules/CountrySelectionUI/Sources/CountrySelectionUI.h deleted file mode 100644 index 3505c8a4d6..0000000000 --- a/submodules/CountrySelectionUI/Sources/CountrySelectionUI.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// CountrySelectionUI.h -// CountrySelectionUI -// -// Created by Peter on 8/11/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for CountrySelectionUI. -FOUNDATION_EXPORT double CountrySelectionUIVersionNumber; - -//! Project version string for CountrySelectionUI. -FOUNDATION_EXPORT const unsigned char CountrySelectionUIVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/Crc32/BUCK b/submodules/Crc32/BUCK index 9e0e9aeb63..5380c8191d 100644 --- a/submodules/Crc32/BUCK +++ b/submodules/Crc32/BUCK @@ -9,7 +9,7 @@ static_library( "Sources/*.h", ]), exported_headers = glob([ - "Sources/*.h", + "PublicHeaders/**/*.h", ]), deps = [ ], diff --git a/submodules/Crc32/BUILD b/submodules/Crc32/BUILD new file mode 100644 index 0000000000..6a635794e7 --- /dev/null +++ b/submodules/Crc32/BUILD @@ -0,0 +1,22 @@ + +objc_library( + name = "Crc32", + enable_modules = True, + module_name = "Crc32", + srcs = glob([ + "Sources/**/*.m", + "Sources/**/*.h", + ]), + hdrs = glob([ + "PublicHeaders/**/*.h", + ]), + includes = [ + "PublicHeaders", + ], + sdk_frameworks = [ + "Foundation", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/Crc32/Sources/Crc32.h b/submodules/Crc32/PublicHeaders/Crc32/Crc32.h similarity index 100% rename from submodules/Crc32/Sources/Crc32.h rename to submodules/Crc32/PublicHeaders/Crc32/Crc32.h diff --git a/submodules/Crc32/Sources/Crc32.m b/submodules/Crc32/Sources/Crc32.m index 7dfded40f5..adc7c6b54d 100644 --- a/submodules/Crc32/Sources/Crc32.m +++ b/submodules/Crc32/Sources/Crc32.m @@ -1,4 +1,4 @@ -#import "Crc32.h" +#import #import diff --git a/submodules/CryptoUtils/BUCK b/submodules/CryptoUtils/BUCK new file mode 100644 index 0000000000..411184de09 --- /dev/null +++ b/submodules/CryptoUtils/BUCK @@ -0,0 +1,19 @@ +load("//Config:buck_rule_macros.bzl", "static_library") + +static_library( + name = "CryptoUtils", + srcs = glob([ + "Sources/*.m", + ]), + headers = glob([ + "Sources/*.h", + ]), + exported_headers = glob([ + "PublicHeaders/**/*.h", + ]), + deps = [ + ], + frameworks = [ + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + ], +) diff --git a/submodules/CryptoUtils/BUILD b/submodules/CryptoUtils/BUILD new file mode 100644 index 0000000000..da7b17b786 --- /dev/null +++ b/submodules/CryptoUtils/BUILD @@ -0,0 +1,21 @@ + +objc_library( + name = "CryptoUtils", + enable_modules = True, + module_name = "CryptoUtils", + srcs = glob([ + "Sources/*.m", + ]), + hdrs = glob([ + "PublicHeaders/**/*.h", + ]), + includes = [ + "PublicHeaders", + ], + sdk_frameworks = [ + "Foundation", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramCore/Sources/Crypto.h b/submodules/CryptoUtils/PublicHeaders/CryptoUtils/Crypto.h similarity index 100% rename from submodules/TelegramCore/Sources/Crypto.h rename to submodules/CryptoUtils/PublicHeaders/CryptoUtils/Crypto.h diff --git a/submodules/TelegramCore/Sources/Crypto.m b/submodules/CryptoUtils/Sources/Crypto.m similarity index 98% rename from submodules/TelegramCore/Sources/Crypto.m rename to submodules/CryptoUtils/Sources/Crypto.m index 86cf407f0c..028031ddbd 100644 --- a/submodules/TelegramCore/Sources/Crypto.m +++ b/submodules/CryptoUtils/Sources/Crypto.m @@ -1,4 +1,4 @@ -#include "Crypto.h" +#include #import diff --git a/submodules/DateSelectionUI/BUILD b/submodules/DateSelectionUI/BUILD new file mode 100644 index 0000000000..a34f436ac4 --- /dev/null +++ b/submodules/DateSelectionUI/BUILD @@ -0,0 +1,20 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "DateSelectionUI", + module_name = "DateSelectionUI", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/AccountContext:AccountContext", + "//submodules/TelegramStringFormatting:TelegramStringFormatting", + "//submodules/TelegramPresentationData:TelegramPresentationData", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/DateSelectionUI/Info.plist b/submodules/DateSelectionUI/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/DateSelectionUI/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/DateSelectionUI/Sources/DateSelectionUI.h b/submodules/DateSelectionUI/Sources/DateSelectionUI.h deleted file mode 100644 index f52f7827f6..0000000000 --- a/submodules/DateSelectionUI/Sources/DateSelectionUI.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// DateSelectionUI.h -// DateSelectionUI -// -// Created by Peter on 8/12/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for DateSelectionUI. -FOUNDATION_EXPORT double DateSelectionUIVersionNumber; - -//! Project version string for DateSelectionUI. -FOUNDATION_EXPORT const unsigned char DateSelectionUIVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/DeleteChatPeerActionSheetItem/BUILD b/submodules/DeleteChatPeerActionSheetItem/BUILD new file mode 100644 index 0000000000..c8da24e78c --- /dev/null +++ b/submodules/DeleteChatPeerActionSheetItem/BUILD @@ -0,0 +1,22 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "DeleteChatPeerActionSheetItem", + module_name = "DeleteChatPeerActionSheetItem", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/SyncCore:SyncCore", + "//submodules/AccountContext:AccountContext", + "//submodules/AvatarNode:AvatarNode", + "//submodules/TelegramPresentationData:TelegramPresentationData", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/DeleteChatPeerActionSheetItem/Info.plist b/submodules/DeleteChatPeerActionSheetItem/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/DeleteChatPeerActionSheetItem/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/DeleteChatPeerActionSheetItem/Sources/DeleteChatPeerActionSheetItem.h b/submodules/DeleteChatPeerActionSheetItem/Sources/DeleteChatPeerActionSheetItem.h deleted file mode 100644 index eff87f3b1f..0000000000 --- a/submodules/DeleteChatPeerActionSheetItem/Sources/DeleteChatPeerActionSheetItem.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// DeleteChatPeerActionSheetItem.h -// DeleteChatPeerActionSheetItem -// -// Created by Peter on 8/13/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for DeleteChatPeerActionSheetItem. -FOUNDATION_EXPORT double DeleteChatPeerActionSheetItemVersionNumber; - -//! Project version string for DeleteChatPeerActionSheetItem. -FOUNDATION_EXPORT const unsigned char DeleteChatPeerActionSheetItemVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/DeviceAccess/BUILD b/submodules/DeviceAccess/BUILD new file mode 100644 index 0000000000..1bfb8443f5 --- /dev/null +++ b/submodules/DeviceAccess/BUILD @@ -0,0 +1,21 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "DeviceAccess", + module_name = "DeviceAccess", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/TelegramCore:TelegramCore", + "//submodules/SyncCore:SyncCore", + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/Display:Display", + "//submodules/AccountContext:AccountContext", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/LegacyComponents:LegacyComponents", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/DeviceAccess/Info.plist b/submodules/DeviceAccess/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/DeviceAccess/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/DeviceAccess/Sources/DeviceAccess.h b/submodules/DeviceAccess/Sources/DeviceAccess.h deleted file mode 100644 index bfb7ce3a32..0000000000 --- a/submodules/DeviceAccess/Sources/DeviceAccess.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// DeviceAccess.h -// DeviceAccess -// -// Created by Peter on 6/13/19. -// Copyright © 2019 Telegram LLP. All rights reserved. -// - -#import - -//! Project version number for DeviceAccess. -FOUNDATION_EXPORT double DeviceAccessVersionNumber; - -//! Project version string for DeviceAccess. -FOUNDATION_EXPORT const unsigned char DeviceAccessVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/DeviceLocationManager/BUILD b/submodules/DeviceLocationManager/BUILD new file mode 100644 index 0000000000..583f811cfb --- /dev/null +++ b/submodules/DeviceLocationManager/BUILD @@ -0,0 +1,15 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "DeviceLocationManager", + module_name = "DeviceLocationManager", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/DeviceProximity/BUCK b/submodules/DeviceProximity/BUCK index 8d7d99755b..94392f0568 100644 --- a/submodules/DeviceProximity/BUCK +++ b/submodules/DeviceProximity/BUCK @@ -7,10 +7,10 @@ static_library( ]), headers = glob([ "Sources/*.h", - ], exclude = ["Sources/DeviceProximity.h"]), + ]), exported_headers = glob([ - "Sources/*.h", - ], exclude = ["Sources/DeviceProximity.h"]), + "PublicHeaders/**/*.h", + ]), frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", "$SDKROOT/System/Library/Frameworks/UIKit.framework", diff --git a/submodules/DeviceProximity/BUILD b/submodules/DeviceProximity/BUILD new file mode 100644 index 0000000000..243c604641 --- /dev/null +++ b/submodules/DeviceProximity/BUILD @@ -0,0 +1,22 @@ + +objc_library( + name = "DeviceProximity", + enable_modules = True, + module_name = "DeviceProximity", + srcs = glob([ + "Sources/*.m", + ]), + hdrs = glob([ + "PublicHeaders/**/*.h", + ]), + includes = [ + "PublicHeaders", + ], + sdk_frameworks = [ + "Foundation", + "UIKit", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/DeviceProximity/Info.plist b/submodules/DeviceProximity/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/DeviceProximity/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/DeviceProximity/PublicHeaders/DeviceProximity/DeviceProximity.h b/submodules/DeviceProximity/PublicHeaders/DeviceProximity/DeviceProximity.h new file mode 100644 index 0000000000..be14dd0c27 --- /dev/null +++ b/submodules/DeviceProximity/PublicHeaders/DeviceProximity/DeviceProximity.h @@ -0,0 +1,5 @@ +#import + +#import + + diff --git a/submodules/DeviceProximity/Sources/DeviceProximityManager.h b/submodules/DeviceProximity/PublicHeaders/DeviceProximity/DeviceProximityManager.h similarity index 100% rename from submodules/DeviceProximity/Sources/DeviceProximityManager.h rename to submodules/DeviceProximity/PublicHeaders/DeviceProximity/DeviceProximityManager.h diff --git a/submodules/DeviceProximity/Sources/DeviceProximity.h b/submodules/DeviceProximity/Sources/DeviceProximity.h deleted file mode 100644 index 449c242823..0000000000 --- a/submodules/DeviceProximity/Sources/DeviceProximity.h +++ /dev/null @@ -1,11 +0,0 @@ -#import - -//! Project version number for DeviceProximity. -FOUNDATION_EXPORT double DeviceProximityVersionNumber; - -//! Project version string for DeviceProximity. -FOUNDATION_EXPORT const unsigned char DeviceProximityVersionString[]; - -#import - - diff --git a/submodules/DeviceProximity/Sources/DeviceProximityManager.m b/submodules/DeviceProximity/Sources/DeviceProximityManager.m index 3061832dcf..94ef975a8d 100644 --- a/submodules/DeviceProximity/Sources/DeviceProximityManager.m +++ b/submodules/DeviceProximity/Sources/DeviceProximityManager.m @@ -1,4 +1,4 @@ -#import "DeviceProximityManager.h" +#import #import diff --git a/submodules/DirectionalPanGesture/BUILD b/submodules/DirectionalPanGesture/BUILD new file mode 100644 index 0000000000..9e62d95bb1 --- /dev/null +++ b/submodules/DirectionalPanGesture/BUILD @@ -0,0 +1,12 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "DirectionalPanGesture", + module_name = "DirectionalPanGesture", + srcs = glob([ + "Sources/**/*.swift", + ]), + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/DirectionalPanGesture/Info.plist b/submodules/DirectionalPanGesture/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/DirectionalPanGesture/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/DirectionalPanGesture/Sources/DirectionalPanGesture.h b/submodules/DirectionalPanGesture/Sources/DirectionalPanGesture.h deleted file mode 100644 index 25ac53274e..0000000000 --- a/submodules/DirectionalPanGesture/Sources/DirectionalPanGesture.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// DirectionalPanGesture.h -// DirectionalPanGesture -// -// Created by Peter on 8/10/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for DirectionalPanGesture. -FOUNDATION_EXPORT double DirectionalPanGestureVersionNumber; - -//! Project version string for DirectionalPanGesture. -FOUNDATION_EXPORT const unsigned char DirectionalPanGestureVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/Display/BUCK b/submodules/Display/BUCK index 64506971d0..6fb664021d 100644 --- a/submodules/Display/BUCK +++ b/submodules/Display/BUCK @@ -3,16 +3,11 @@ load("//Config:buck_rule_macros.bzl", "framework") framework( name = "Display", srcs = glob([ - "Display/**/*.swift", - "Display/**/*.m", + "Source/**/*.swift", ]), - headers = glob([ - "Display/*.h", - ], exclude = ["Display/Display.h"]), - exported_headers = glob([ - "Display/*.h", - ], exclude = ["Display/Display.h"]), deps = [ + "//submodules/ObjCRuntimeUtils:ObjCRuntimeUtils", + "//submodules/UIKitRuntimeUtils:UIKitRuntimeUtils", "//submodules/AsyncDisplayKit:AsyncDisplayKit#shared", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared", "//submodules/AppBundle:AppBundle", diff --git a/submodules/Display/BUILD b/submodules/Display/BUILD new file mode 100644 index 0000000000..277d09ae20 --- /dev/null +++ b/submodules/Display/BUILD @@ -0,0 +1,20 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "Display", + module_name = "Display", + srcs = glob([ + "Source/**/*.swift", + ]), + deps = [ + "//submodules/ObjCRuntimeUtils:ObjCRuntimeUtils", + "//submodules/UIKitRuntimeUtils:UIKitRuntimeUtils", + "//submodules/AppBundle:AppBundle", + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/Markdown:Markdown", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/Display/Display/CASeeThroughTracingLayer.h b/submodules/Display/Display/CASeeThroughTracingLayer.h deleted file mode 100644 index 8c6ff3de8e..0000000000 --- a/submodules/Display/Display/CASeeThroughTracingLayer.h +++ /dev/null @@ -1,9 +0,0 @@ -#import - -@interface CASeeThroughTracingLayer : CALayer - -@end - -@interface CASeeThroughTracingView : UIView - -@end diff --git a/submodules/Display/Display/CASeeThroughTracingLayer.m b/submodules/Display/Display/CASeeThroughTracingLayer.m deleted file mode 100644 index 79ff8d3631..0000000000 --- a/submodules/Display/Display/CASeeThroughTracingLayer.m +++ /dev/null @@ -1,57 +0,0 @@ -#import "CASeeThroughTracingLayer.h" - -@interface CASeeThroughTracingLayer () { - CGPoint _parentOffset; -} - -@end - -@implementation CASeeThroughTracingLayer - -- (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key { - [super addAnimation:anim forKey:key]; -} - -- (void)setFrame:(CGRect)frame { - [super setFrame:frame]; - - [self _mirrorTransformToSublayers]; -} - -- (void)setBounds:(CGRect)bounds { - [super setBounds:bounds]; - - [self _mirrorTransformToSublayers]; -} - -- (void)setPosition:(CGPoint)position { - [super setPosition:position]; - - [self _mirrorTransformToSublayers]; -} - -- (void)_mirrorTransformToSublayers { - CGRect bounds = self.bounds; - CGPoint position = self.position; - - CGPoint sublayerParentOffset = _parentOffset; - sublayerParentOffset.x += position.x - (bounds.size.width) / 2.0f; - sublayerParentOffset.y += position.y - (bounds.size.width) / 2.0f; - - for (CALayer *sublayer in self.sublayers) { - if ([sublayer isKindOfClass:[CASeeThroughTracingLayer class]]) { - ((CASeeThroughTracingLayer *)sublayer)->_parentOffset = sublayerParentOffset; - [(CASeeThroughTracingLayer *)sublayer _mirrorTransformToSublayers]; - } - } -} - -@end - -@implementation CASeeThroughTracingView - -+ (Class)layerClass { - return [CASeeThroughTracingLayer class]; -} - -@end diff --git a/submodules/Display/Display/CATracingLayer.h b/submodules/Display/Display/CATracingLayer.h deleted file mode 100644 index 22037b19a4..0000000000 --- a/submodules/Display/Display/CATracingLayer.h +++ /dev/null @@ -1,34 +0,0 @@ -#import - -@interface CATracingLayer : CALayer - -@end - -@interface CATracingLayerInfo : NSObject - -@property (nonatomic, readonly) bool shouldBeAdjustedToInverseTransform; -@property (nonatomic, weak, readonly) id _Nullable userData; -@property (nonatomic, readonly) int32_t tracingTag; -@property (nonatomic, readonly) int32_t disableChildrenTracingTags; - -- (instancetype _Nonnull)initWithShouldBeAdjustedToInverseTransform:(bool)shouldBeAdjustedToInverseTransform userData:(id _Nullable)userData tracingTag:(int32_t)tracingTag disableChildrenTracingTags:(int32_t)disableChildrenTracingTags; - -@end - -@interface CALayer (Tracing) - -- (CATracingLayerInfo * _Nullable)traceableInfo; -- (void)setTraceableInfo:(CATracingLayerInfo * _Nullable)info; - -- (bool)hasPositionOrOpacityAnimations; -- (bool)hasPositionAnimations; - -- (void)setInvalidateTracingSublayers:(void (^_Nullable)())block; -- (NSArray *> * _Nonnull)traceableLayerSurfacesWithTag:(int32_t)tracingTag; -- (void)adjustTraceableLayerTransforms:(CGSize)offset; - -- (void)setPositionAnimationMirrorTarget:(CALayer * _Nullable)animationMirrorTarget; - -- (void)invalidateUpTheTree; - -@end diff --git a/submodules/Display/Display/CATracingLayer.m b/submodules/Display/Display/CATracingLayer.m deleted file mode 100644 index dd463b5b56..0000000000 --- a/submodules/Display/Display/CATracingLayer.m +++ /dev/null @@ -1,364 +0,0 @@ -#import "CATracingLayer.h" - -#import "RuntimeUtils.h" - -static void *CATracingLayerInvalidatedKey = &CATracingLayerInvalidatedKey; -static void *CATracingLayerIsInvalidatedBlock = &CATracingLayerIsInvalidatedBlock; -static void *CATracingLayerTraceableInfoKey = &CATracingLayerTraceableInfoKey; -static void *CATracingLayerPositionAnimationMirrorTarget = &CATracingLayerPositionAnimationMirrorTarget; - -@implementation CALayer (Tracing) - -- (void)setInvalidateTracingSublayers:(void (^_Nullable)())block { - [self setAssociatedObject:[block copy] forKey:CATracingLayerIsInvalidatedBlock]; -} - -- (void (^_Nullable)())invalidateTracingSublayers { - return [self associatedObjectForKey:CATracingLayerIsInvalidatedBlock]; -} - -- (bool)isTraceable { - return [self associatedObjectForKey:CATracingLayerTraceableInfoKey] != nil || [self isKindOfClass:[CATracingLayer class]]; -} - -- (CATracingLayerInfo * _Nullable)traceableInfo { - return [self associatedObjectForKey:CATracingLayerTraceableInfoKey]; -} - -- (void)setTraceableInfo:(CATracingLayerInfo * _Nullable)info { - [self setAssociatedObject:info forKey:CATracingLayerTraceableInfoKey]; -} - -- (bool)hasPositionOrOpacityAnimations { - return [self animationForKey:@"position"] != nil || [self animationForKey:@"bounds"] != nil || [self animationForKey:@"sublayerTransform"] != nil || [self animationForKey:@"opacity"] != nil; -} - -- (bool)hasPositionAnimations { - return [self animationForKey:@"position"] != nil || [self animationForKey:@"bounds"] != nil; -} - -static void traceLayerSurfaces(int32_t tracingTag, int depth, CALayer * _Nonnull layer, NSMutableDictionary *> *layersByDepth, bool skipIfNoTraceableSublayers) { - bool hadTraceableSublayers = false; - for (CALayer *sublayer in layer.sublayers.reverseObjectEnumerator) { - CATracingLayerInfo *sublayerTraceableInfo = [sublayer traceableInfo]; - if (sublayerTraceableInfo != nil && sublayerTraceableInfo.tracingTag == tracingTag) { - NSMutableArray *array = layersByDepth[@(depth)]; - if (array == nil) { - array = [[NSMutableArray alloc] init]; - layersByDepth[@(depth)] = array; - } - [array addObject:sublayer]; - hadTraceableSublayers = true; - } - if (sublayerTraceableInfo.disableChildrenTracingTags & tracingTag) { - return; - } - } - - if (!skipIfNoTraceableSublayers || !hadTraceableSublayers) { - for (CALayer *sublayer in layer.sublayers.reverseObjectEnumerator) { - if ([sublayer isKindOfClass:[CATracingLayer class]]) { - traceLayerSurfaces(tracingTag, depth + 1, sublayer, layersByDepth, hadTraceableSublayers); - } - } - } -} - -- (NSArray *> * _Nonnull)traceableLayerSurfacesWithTag:(int32_t)tracingTag { - NSMutableDictionary *> *layersByDepth = [[NSMutableDictionary alloc] init]; - - traceLayerSurfaces(tracingTag, 0, self, layersByDepth, false); - - NSMutableArray *> *result = [[NSMutableArray alloc] init]; - - for (id key in [[layersByDepth allKeys] sortedArrayUsingSelector:@selector(compare:)]) { - [result addObject:layersByDepth[key]]; - } - - return result; -} - -- (void)adjustTraceableLayerTransforms:(CGSize)offset { - CGRect frame = self.frame; - CGSize sublayerOffset = CGSizeMake(frame.origin.x + offset.width, frame.origin.y + offset.height); - for (CALayer *sublayer in self.sublayers) { - CATracingLayerInfo *sublayerTraceableInfo = [sublayer traceableInfo]; - if (sublayerTraceableInfo != nil && sublayerTraceableInfo.shouldBeAdjustedToInverseTransform) { - sublayer.sublayerTransform = CATransform3DMakeTranslation(-sublayerOffset.width, -sublayerOffset.height, 0.0f); - } else if ([sublayer isKindOfClass:[CATracingLayer class]]) { - [(CATracingLayer *)sublayer adjustTraceableLayerTransforms:sublayerOffset]; - } - } -} - -- (CALayer * _Nullable)animationMirrorTarget { - return [self associatedObjectForKey:CATracingLayerPositionAnimationMirrorTarget]; -} - -- (void)setPositionAnimationMirrorTarget:(CALayer * _Nullable)animationMirrorTarget { - [self setAssociatedObject:animationMirrorTarget forKey:CATracingLayerPositionAnimationMirrorTarget associationPolicy:NSObjectAssociationPolicyRetain]; -} - -- (void)invalidateUpTheTree { - CALayer *superlayer = self; - while (true) { - if (superlayer == nil) { - break; - } - - void (^block)() = [superlayer invalidateTracingSublayers]; - if (block != nil) { - block(); - } - - superlayer = superlayer.superlayer; - } -} - -@end - -@interface CATracingLayerAnimationDelegate : NSObject { - id _delegate; - void (^_animationStopped)(); -} - -@end - -@implementation CATracingLayerAnimationDelegate - -- (instancetype)initWithDelegate:(id)delegate animationStopped:(void (^_Nonnull)())animationStopped { - _delegate = delegate; - _animationStopped = [animationStopped copy]; - return self; -} - -- (void)animationDidStart:(CAAnimation *)anim { - if ([_delegate respondsToSelector:@selector(animationDidStart:)]) { - [(id)_delegate animationDidStart:anim]; - } -} - -- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { - if ([_delegate respondsToSelector:@selector(animationDidStop:finished:)]) { - [(id)_delegate animationDidStop:anim finished:flag]; - } - - if (_animationStopped) { - _animationStopped(); - } -} - -@end - -@interface CATracingLayer () - -@property (nonatomic) bool isInvalidated; - -@end - -@implementation CATracingLayer - -- (void)setNeedsDisplay { -} - -- (void)displayIfNeeded { -} - -- (bool)isInvalidated { - return [[self associatedObjectForKey:CATracingLayerInvalidatedKey] intValue] != 0; -} - -- (void)setIsInvalidated:(bool)isInvalidated { - [self setAssociatedObject: isInvalidated ? @1 : @0 forKey:CATracingLayerInvalidatedKey]; -} - -- (void)setPosition:(CGPoint)position { - [super setPosition:position]; - - [self invalidateUpTheTree]; -} - -- (void)setOpacity:(float)opacity { - [super setOpacity:opacity]; - - [self invalidateUpTheTree]; -} - -- (void)addSublayer:(CALayer *)layer { - [super addSublayer:layer]; - - if ([layer isTraceable] || [layer isKindOfClass:[CATracingLayer class]]) { - [self invalidateUpTheTree]; - } -} - -- (void)insertSublayer:(CALayer *)layer atIndex:(unsigned)idx { - [super insertSublayer:layer atIndex:idx]; - - if ([layer isTraceable] || [layer isKindOfClass:[CATracingLayer class]]) { - [self invalidateUpTheTree]; - } -} - -- (void)insertSublayer:(CALayer *)layer below:(nullable CALayer *)sibling { - [super insertSublayer:layer below:sibling]; - - if ([layer isTraceable] || [layer isKindOfClass:[CATracingLayer class]]) { - [self invalidateUpTheTree]; - } -} - -- (void)insertSublayer:(CALayer *)layer above:(nullable CALayer *)sibling { - [super insertSublayer:layer above:sibling]; - - if ([layer isTraceable] || [layer isKindOfClass:[CATracingLayer class]]) { - [self invalidateUpTheTree]; - } -} - -- (void)replaceSublayer:(CALayer *)layer with:(CALayer *)layer2 { - [super replaceSublayer:layer with:layer2]; - - if ([layer isTraceable] || [layer2 isTraceable]) { - [self invalidateUpTheTree]; - } -} - -- (void)removeFromSuperlayer { - if ([self isTraceable]) { - [self invalidateUpTheTree]; - } - - [super removeFromSuperlayer]; -} - -- (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key { - if ([anim isKindOfClass:[CABasicAnimation class]]) { - if (false && [key isEqualToString:@"bounds.origin.y"]) { - CABasicAnimation *animCopy = [anim copy]; - CGFloat from = [animCopy.fromValue floatValue]; - CGFloat to = [animCopy.toValue floatValue]; - - animCopy.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0.0, to - from, 0.0f)]; - animCopy.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity]; - animCopy.keyPath = @"sublayerTransform"; - - __weak CATracingLayer *weakSelf = self; - anim.delegate = [[CATracingLayerAnimationDelegate alloc] initWithDelegate:anim.delegate animationStopped:^{ - __strong CATracingLayer *strongSelf = weakSelf; - if (strongSelf != nil) { - [strongSelf invalidateUpTheTree]; - } - }]; - - [super addAnimation:anim forKey:key]; - - CABasicAnimation *positionAnimCopy = [animCopy copy]; - positionAnimCopy.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0.0, 0.0, 0.0f)]; - positionAnimCopy.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity]; - positionAnimCopy.additive = true; - positionAnimCopy.delegate = [[CATracingLayerAnimationDelegate alloc] initWithDelegate:anim.delegate animationStopped:^{ - __strong CATracingLayer *strongSelf = weakSelf; - if (strongSelf != nil) { - [strongSelf invalidateUpTheTree]; - } - }]; - - [self invalidateUpTheTree]; - - [self mirrorAnimationDownTheTree:animCopy key:@"sublayerTransform"]; - [self mirrorPositionAnimationDownTheTree:positionAnimCopy key:@"sublayerTransform"]; - } else if ([key isEqualToString:@"position"]) { - CABasicAnimation *animCopy = [anim copy]; - CGPoint from = [animCopy.fromValue CGPointValue]; - CGPoint to = [animCopy.toValue CGPointValue]; - - animCopy.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(to.x - from.x, to.y - from.y, 0.0f)]; - animCopy.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity]; - animCopy.keyPath = @"sublayerTransform"; - - __weak CATracingLayer *weakSelf = self; - anim.delegate = [[CATracingLayerAnimationDelegate alloc] initWithDelegate:anim.delegate animationStopped:^{ - __strong CATracingLayer *strongSelf = weakSelf; - if (strongSelf != nil) { - [strongSelf invalidateUpTheTree]; - } - }]; - - [super addAnimation:anim forKey:key]; - - CABasicAnimation *positionAnimCopy = [animCopy copy]; - positionAnimCopy.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(-to.x + from.x, 0.0, 0.0f)]; - positionAnimCopy.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity]; - positionAnimCopy.additive = true; - positionAnimCopy.delegate = [[CATracingLayerAnimationDelegate alloc] initWithDelegate:anim.delegate animationStopped:^{ - __strong CATracingLayer *strongSelf = weakSelf; - if (strongSelf != nil) { - [strongSelf invalidateUpTheTree]; - } - }]; - - [self invalidateUpTheTree]; - - [self mirrorAnimationDownTheTree:animCopy key:@"sublayerTransform"]; - [self mirrorPositionAnimationDownTheTree:positionAnimCopy key:@"sublayerTransform"]; - } else if ([key isEqualToString:@"opacity"]) { - __weak CATracingLayer *weakSelf = self; - anim.delegate = [[CATracingLayerAnimationDelegate alloc] initWithDelegate:anim.delegate animationStopped:^{ - __strong CATracingLayer *strongSelf = weakSelf; - if (strongSelf != nil) { - [strongSelf invalidateUpTheTree]; - } - }]; - - [super addAnimation:anim forKey:key]; - - [self invalidateUpTheTree]; - } else { - [super addAnimation:anim forKey:key]; - } - } else { - [super addAnimation:anim forKey:key]; - } -} - -- (void)mirrorPositionAnimationDownTheTree:(CAAnimation *)animation key:(NSString *)key { - if ([animation isKindOfClass:[CABasicAnimation class]]) { - if ([((CABasicAnimation *)animation).keyPath isEqualToString:@"sublayerTransform"]) { - CALayer *positionAnimationMirrorTarget = [self animationMirrorTarget]; - if (positionAnimationMirrorTarget != nil) { - [positionAnimationMirrorTarget addAnimation:[animation copy] forKey:key]; - } - } - } -} - -- (void)mirrorAnimationDownTheTree:(CAAnimation *)animation key:(NSString *)key { - for (CALayer *sublayer in self.sublayers) { - CATracingLayerInfo *traceableInfo = [sublayer traceableInfo]; - if (traceableInfo != nil && traceableInfo.shouldBeAdjustedToInverseTransform) { - [sublayer addAnimation:[animation copy] forKey:key]; - } - - if ([sublayer isKindOfClass:[CATracingLayer class]]) { - [(CATracingLayer *)sublayer mirrorAnimationDownTheTree:animation key:key]; - } - } -} - -@end - -@implementation CATracingLayerInfo - -- (instancetype _Nonnull)initWithShouldBeAdjustedToInverseTransform:(bool)shouldBeAdjustedToInverseTransform userData:(id _Nullable)userData tracingTag:(int32_t)tracingTag disableChildrenTracingTags:(int32_t)disableChildrenTracingTags { - self = [super init]; - if (self != nil) { - _shouldBeAdjustedToInverseTransform = shouldBeAdjustedToInverseTransform; - _userData = userData; - _tracingTag = tracingTag; - _disableChildrenTracingTags = disableChildrenTracingTags; - } - return self; -} - -@end diff --git a/submodules/Display/Display/Display.h b/submodules/Display/Display/Display.h deleted file mode 100644 index e013c37f97..0000000000 --- a/submodules/Display/Display/Display.h +++ /dev/null @@ -1,33 +0,0 @@ -// -// Display.h -// Display -// -// Created by Peter on 29/07/15. -// Copyright © 2015 Telegram. All rights reserved. -// - -#import - -//! Project version number for Display. -FOUNDATION_EXPORT double DisplayVersionNumber; - -//! Project version string for Display. -FOUNDATION_EXPORT const unsigned char DisplayVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import diff --git a/submodules/Display/Display/Info.plist b/submodules/Display/Display/Info.plist deleted file mode 100644 index d3de8eefb6..0000000000 --- a/submodules/Display/Display/Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - NSPrincipalClass - - - diff --git a/submodules/Display/Display/InteractiveTransitionGestureRecognizer.swift b/submodules/Display/Display/InteractiveTransitionGestureRecognizer.swift deleted file mode 100644 index 6462a0d325..0000000000 --- a/submodules/Display/Display/InteractiveTransitionGestureRecognizer.swift +++ /dev/null @@ -1,111 +0,0 @@ -import Foundation -import UIKit - -private func hasHorizontalGestures(_ view: UIView, point: CGPoint?) -> Bool { - if view.disablesInteractiveTransitionGestureRecognizer { - return true - } - if let disablesInteractiveTransitionGestureRecognizerNow = view.disablesInteractiveTransitionGestureRecognizerNow, disablesInteractiveTransitionGestureRecognizerNow() { - return true - } - - if let point = point, let test = view.interactiveTransitionGestureRecognizerTest, test(point) { - return true - } - - if let view = view as? ListViewBackingView { - let transform = view.transform - let angle: Double = Double(atan2f(Float(transform.b), Float(transform.a))) - let term1: Double = abs(angle - Double.pi / 2.0) - let term2: Double = abs(angle + Double.pi / 2.0) - let term3: Double = abs(angle - Double.pi * 3.0 / 2.0) - if term1 < 0.001 || term2 < 0.001 || term3 < 0.001 { - return true - } - } - - if let superview = view.superview { - return hasHorizontalGestures(superview, point: point != nil ? view.convert(point!, to: superview) : nil) - } else { - return false - } -} - -public struct InteractiveTransitionGestureRecognizerDirections: OptionSet { - public var rawValue: Int - - public init(rawValue: Int) { - self.rawValue = rawValue - } - - public static let left = InteractiveTransitionGestureRecognizerDirections(rawValue: 1 << 0) - public static let right = InteractiveTransitionGestureRecognizerDirections(rawValue: 1 << 1) -} - -public class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer { - private let allowedDirections: () -> InteractiveTransitionGestureRecognizerDirections - - private var validatedGesture = false - private var firstLocation: CGPoint = CGPoint() - private var currentAllowedDirections: InteractiveTransitionGestureRecognizerDirections = [] - - public init(target: Any?, action: Selector?, allowedDirections: @escaping () -> InteractiveTransitionGestureRecognizerDirections) { - self.allowedDirections = allowedDirections - - super.init(target: target, action: action) - - self.maximumNumberOfTouches = 1 - } - - override public func reset() { - super.reset() - - self.validatedGesture = false - self.currentAllowedDirections = [] - } - - override public func touchesBegan(_ touches: Set, with event: UIEvent) { - self.currentAllowedDirections = self.allowedDirections() - if self.currentAllowedDirections.isEmpty { - self.state = .failed - return - } - - super.touchesBegan(touches, with: event) - - let touch = touches.first! - self.firstLocation = touch.location(in: self.view) - - if let target = self.view?.hitTest(self.firstLocation, with: event) { - if hasHorizontalGestures(target, point: self.view?.convert(self.firstLocation, to: target)) { - self.state = .cancelled - } - } - } - - override public func touchesMoved(_ touches: Set, with event: UIEvent) { - let location = touches.first!.location(in: self.view) - let translation = CGPoint(x: location.x - firstLocation.x, y: location.y - firstLocation.y) - - let absTranslationX: CGFloat = abs(translation.x) - let absTranslationY: CGFloat = abs(translation.y) - - if !self.validatedGesture { - if self.currentAllowedDirections.contains(.right) && self.firstLocation.x < 16.0 { - self.validatedGesture = true - } else if !self.currentAllowedDirections.contains(.left) && translation.x < 0.0 { - self.state = .failed - } else if !self.currentAllowedDirections.contains(.right) && translation.x > 0.0 { - self.state = .failed - } else if absTranslationY > 2.0 && absTranslationY > absTranslationX * 2.0 { - self.state = .failed - } else if absTranslationX > 2.0 && absTranslationY * 2.0 < absTranslationX { - self.validatedGesture = true - } - } - - if validatedGesture { - super.touchesMoved(touches, with: event) - } - } -} diff --git a/submodules/Display/Display/NavigationBarProxy.h b/submodules/Display/Display/NavigationBarProxy.h deleted file mode 100644 index 9936b4726a..0000000000 --- a/submodules/Display/Display/NavigationBarProxy.h +++ /dev/null @@ -1,7 +0,0 @@ -#import - -@interface NavigationBarProxy : UINavigationBar - -@property (nonatomic, copy) void (^setItemsProxy)(NSArray *, NSArray *, bool); - -@end diff --git a/submodules/Display/Display/NavigationBarProxy.m b/submodules/Display/Display/NavigationBarProxy.m deleted file mode 100644 index 9f5700c13b..0000000000 --- a/submodules/Display/Display/NavigationBarProxy.m +++ /dev/null @@ -1,67 +0,0 @@ -#import "NavigationBarProxy.h" - -@interface NavigationBarProxy () -{ - NSArray *_items; -} - -@end - -@implementation NavigationBarProxy - -- (instancetype)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (self != nil) - { - } - return self; -} - -- (void)pushNavigationItem:(UINavigationItem *)item animated:(BOOL)animated -{ - [self setItems:[[self items] arrayByAddingObject:item] animated:animated]; -} - -- (UINavigationItem *)popNavigationItemAnimated:(BOOL)animated -{ - NSMutableArray *items = [[NSMutableArray alloc] initWithArray:[self items]]; - UINavigationItem *lastItem = [items lastObject]; - [items removeLastObject]; - [self setItems:items animated:animated]; - return lastItem; -} - -- (UINavigationItem *)topItem -{ - return [[self items] lastObject]; -} - -- (UINavigationItem *)backItem -{ - NSLog(@"backItem"); - return nil; -} - -- (NSArray *)items -{ - if (_items == nil) - return @[]; - return _items; -} - -- (void)setItems:(NSArray *)items -{ - [self setItems:items animated:false]; -} - -- (void)setItems:(NSArray *)items animated:(BOOL)animated -{ - NSArray *previousItems = _items; - _items = items; - - if (_setItemsProxy) - _setItemsProxy(previousItems, items, animated); -} - -@end diff --git a/submodules/Display/Display/NavigationControllerProxy.h b/submodules/Display/Display/NavigationControllerProxy.h deleted file mode 100644 index 02e4fb6208..0000000000 --- a/submodules/Display/Display/NavigationControllerProxy.h +++ /dev/null @@ -1,7 +0,0 @@ -#import - -@interface NavigationControllerProxy : UINavigationController - -- (instancetype)init; - -@end diff --git a/submodules/Display/Display/NavigationControllerProxy.m b/submodules/Display/Display/NavigationControllerProxy.m deleted file mode 100644 index 6a86a5062f..0000000000 --- a/submodules/Display/Display/NavigationControllerProxy.m +++ /dev/null @@ -1,15 +0,0 @@ -#import "NavigationControllerProxy.h" - -#import "NavigationBarProxy.h" - -@implementation NavigationControllerProxy - -- (instancetype)init -{ - self = [super initWithNavigationBarClass:[NavigationBarProxy class] toolbarClass:[UIToolbar class]]; - if (self != nil) { - } - return self; -} - -@end diff --git a/submodules/Display/Display/UIKitUtils.h b/submodules/Display/Display/UIKitUtils.h deleted file mode 100644 index 71c6a986d7..0000000000 --- a/submodules/Display/Display/UIKitUtils.h +++ /dev/null @@ -1,16 +0,0 @@ -#import -#import - -@interface UIView (AnimationUtils) - -+ (double)animationDurationFactor; - -@end - -CABasicAnimation * _Nonnull makeSpringAnimation(NSString * _Nonnull keyPath); -CABasicAnimation * _Nonnull makeSpringBounceAnimation(NSString * _Nonnull keyPath, CGFloat initialVelocity, CGFloat damping); -CGFloat springAnimationValueAt(CABasicAnimation * _Nonnull animation, CGFloat t); - -void testZoomBlurEffect(UIVisualEffect *effect); -UIBlurEffect *makeCustomZoomBlurEffect(); -void applySmoothRoundedCorners(CALayer * _Nonnull layer); diff --git a/submodules/Display/DisplayTests/DisplayTests.swift b/submodules/Display/DisplayTests/DisplayTests.swift deleted file mode 100644 index 3f8d2f268a..0000000000 --- a/submodules/Display/DisplayTests/DisplayTests.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// DisplayTests.swift -// DisplayTests -// -// Created by Peter on 29/07/15. -// Copyright © 2015 Telegram. All rights reserved. -// - -import XCTest -@testable import Display - -class DisplayTests: XCTestCase { - - override func setUp() { - super.setUp() - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() - } - - func testExample() { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - - func testPerformanceExample() { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } - } - -} diff --git a/submodules/Display/DisplayTests/Info.plist b/submodules/Display/DisplayTests/Info.plist deleted file mode 100644 index ba72822e87..0000000000 --- a/submodules/Display/DisplayTests/Info.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - - diff --git a/submodules/Display/Display/ASTransformLayerNode.swift b/submodules/Display/Source/ASTransformLayerNode.swift similarity index 100% rename from submodules/Display/Display/ASTransformLayerNode.swift rename to submodules/Display/Source/ASTransformLayerNode.swift diff --git a/submodules/Display/Display/Accessibility.swift b/submodules/Display/Source/Accessibility.swift similarity index 100% rename from submodules/Display/Display/Accessibility.swift rename to submodules/Display/Source/Accessibility.swift diff --git a/submodules/Display/Display/AccessibilityAreaNode.swift b/submodules/Display/Source/AccessibilityAreaNode.swift similarity index 100% rename from submodules/Display/Display/AccessibilityAreaNode.swift rename to submodules/Display/Source/AccessibilityAreaNode.swift diff --git a/submodules/Display/Display/ActionSheetButtonItem.swift b/submodules/Display/Source/ActionSheetButtonItem.swift similarity index 95% rename from submodules/Display/Display/ActionSheetButtonItem.swift rename to submodules/Display/Source/ActionSheetButtonItem.swift index 89abbaf404..5c02b3c448 100644 --- a/submodules/Display/Display/ActionSheetButtonItem.swift +++ b/submodules/Display/Source/ActionSheetButtonItem.swift @@ -53,7 +53,7 @@ public class ActionSheetButtonNode: ActionSheetItemNode { private var item: ActionSheetButtonItem? private let button: HighlightTrackingButton - private let label: ASTextNode + private let label: ImmediateTextNode private let accessibilityArea: AccessibilityAreaNode override public init(theme: ActionSheetControllerTheme) { @@ -65,11 +65,11 @@ public class ActionSheetButtonNode: ActionSheetItemNode { self.button = HighlightTrackingButton() self.button.isAccessibilityElement = false - self.label = ASTextNode() + self.label = ImmediateTextNode() self.label.isUserInteractionEnabled = false self.label.maximumNumberOfLines = 1 self.label.displaysAsynchronously = false - self.label.truncationMode = .byTruncatingTail + self.label.truncationType = .end self.accessibilityArea = AccessibilityAreaNode() @@ -147,7 +147,7 @@ public class ActionSheetButtonNode: ActionSheetItemNode { self.button.frame = CGRect(origin: CGPoint(), size: size) - let labelSize = self.label.measure(CGSize(width: max(1.0, size.width - 10.0), height: size.height)) + let labelSize = self.label.updateLayout(CGSize(width: max(1.0, size.width - 10.0), height: size.height)) self.label.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - labelSize.width) / 2.0), y: floorToScreenPixels((size.height - labelSize.height) / 2.0)), size: labelSize) self.accessibilityArea.frame = CGRect(origin: CGPoint(), size: size) } diff --git a/submodules/Display/Display/ActionSheetCheckboxItem.swift b/submodules/Display/Source/ActionSheetCheckboxItem.swift similarity index 99% rename from submodules/Display/Display/ActionSheetCheckboxItem.swift rename to submodules/Display/Source/ActionSheetCheckboxItem.swift index a24220e064..e4472f0539 100644 --- a/submodules/Display/Display/ActionSheetCheckboxItem.swift +++ b/submodules/Display/Source/ActionSheetCheckboxItem.swift @@ -73,7 +73,6 @@ public class ActionSheetCheckboxItemNode: ActionSheetItemNode { self.checkNode = ASImageNode() self.checkNode.isUserInteractionEnabled = false - self.checkNode.displayWithoutProcessing = true self.checkNode.displaysAsynchronously = false self.checkNode.image = generateImage(CGSize(width: 14.0, height: 11.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) diff --git a/submodules/Display/Display/ActionSheetController.swift b/submodules/Display/Source/ActionSheetController.swift similarity index 100% rename from submodules/Display/Display/ActionSheetController.swift rename to submodules/Display/Source/ActionSheetController.swift diff --git a/submodules/Display/Display/ActionSheetControllerNode.swift b/submodules/Display/Source/ActionSheetControllerNode.swift similarity index 100% rename from submodules/Display/Display/ActionSheetControllerNode.swift rename to submodules/Display/Source/ActionSheetControllerNode.swift diff --git a/submodules/Display/Display/ActionSheetItem.swift b/submodules/Display/Source/ActionSheetItem.swift similarity index 100% rename from submodules/Display/Display/ActionSheetItem.swift rename to submodules/Display/Source/ActionSheetItem.swift diff --git a/submodules/Display/Display/ActionSheetItemGroup.swift b/submodules/Display/Source/ActionSheetItemGroup.swift similarity index 100% rename from submodules/Display/Display/ActionSheetItemGroup.swift rename to submodules/Display/Source/ActionSheetItemGroup.swift diff --git a/submodules/Display/Display/ActionSheetItemGroupNode.swift b/submodules/Display/Source/ActionSheetItemGroupNode.swift similarity index 100% rename from submodules/Display/Display/ActionSheetItemGroupNode.swift rename to submodules/Display/Source/ActionSheetItemGroupNode.swift diff --git a/submodules/Display/Display/ActionSheetItemGroupsContainerNode.swift b/submodules/Display/Source/ActionSheetItemGroupsContainerNode.swift similarity index 100% rename from submodules/Display/Display/ActionSheetItemGroupsContainerNode.swift rename to submodules/Display/Source/ActionSheetItemGroupsContainerNode.swift diff --git a/submodules/Display/Display/ActionSheetItemNode.swift b/submodules/Display/Source/ActionSheetItemNode.swift similarity index 100% rename from submodules/Display/Display/ActionSheetItemNode.swift rename to submodules/Display/Source/ActionSheetItemNode.swift diff --git a/submodules/Display/Display/ActionSheetSwitchItem.swift b/submodules/Display/Source/ActionSheetSwitchItem.swift similarity index 94% rename from submodules/Display/Display/ActionSheetSwitchItem.swift rename to submodules/Display/Source/ActionSheetSwitchItem.swift index 7dddc07701..110a4b4277 100644 --- a/submodules/Display/Display/ActionSheetSwitchItem.swift +++ b/submodules/Display/Source/ActionSheetSwitchItem.swift @@ -35,7 +35,7 @@ public class ActionSheetSwitchNode: ActionSheetItemNode { private var item: ActionSheetSwitchItem? private let button: HighlightTrackingButton - private let label: ASTextNode + private let label: ImmediateTextNode private let switchNode: SwitchNode private let accessibilityArea: AccessibilityAreaNode @@ -47,11 +47,11 @@ public class ActionSheetSwitchNode: ActionSheetItemNode { self.button = HighlightTrackingButton() self.button.isAccessibilityElement = false - self.label = ASTextNode() + self.label = ImmediateTextNode() self.label.isUserInteractionEnabled = false self.label.maximumNumberOfLines = 1 self.label.displaysAsynchronously = false - self.label.truncationMode = .byTruncatingTail + self.label.truncationType = .end self.label.isAccessibilityElement = false self.switchNode = SwitchNode() @@ -115,7 +115,7 @@ public class ActionSheetSwitchNode: ActionSheetItemNode { self.button.frame = CGRect(origin: CGPoint(), size: size) - let labelSize = self.label.measure(CGSize(width: max(1.0, size.width - 51.0 - 16.0 * 2.0), height: size.height)) + let labelSize = self.label.updateLayout(CGSize(width: max(1.0, size.width - 51.0 - 16.0 * 2.0), height: size.height)) self.label.frame = CGRect(origin: CGPoint(x: 16.0, y: floorToScreenPixels((size.height - labelSize.height) / 2.0)), size: labelSize) let switchSize = CGSize(width: 51.0, height: 31.0) diff --git a/submodules/Display/Display/ActionSheetTextItem.swift b/submodules/Display/Source/ActionSheetTextItem.swift similarity index 87% rename from submodules/Display/Display/ActionSheetTextItem.swift rename to submodules/Display/Source/ActionSheetTextItem.swift index 65f0be42b0..c49add5114 100644 --- a/submodules/Display/Display/ActionSheetTextItem.swift +++ b/submodules/Display/Source/ActionSheetTextItem.swift @@ -32,7 +32,7 @@ public class ActionSheetTextNode: ActionSheetItemNode { private var item: ActionSheetTextItem? - private let label: ASTextNode + private let label: ImmediateTextNode private let accessibilityArea: AccessibilityAreaNode @@ -40,11 +40,11 @@ public class ActionSheetTextNode: ActionSheetItemNode { self.theme = theme self.defaultFont = Font.regular(floor(theme.baseFontSize * 13.0 / 17.0)) - self.label = ASTextNode() + self.label = ImmediateTextNode() self.label.isUserInteractionEnabled = false self.label.maximumNumberOfLines = 0 self.label.displaysAsynchronously = false - self.label.truncationMode = .byTruncatingTail + self.label.truncationType = .end self.label.isAccessibilityElement = false self.accessibilityArea = AccessibilityAreaNode() @@ -70,7 +70,7 @@ public class ActionSheetTextNode: ActionSheetItemNode { } public override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { - let labelSize = self.label.measure(CGSize(width: max(1.0, constrainedSize.width - 20.0), height: constrainedSize.height)) + let labelSize = self.label.updateLayout(CGSize(width: max(1.0, constrainedSize.width - 20.0), height: constrainedSize.height)) return CGSize(width: constrainedSize.width, height: max(57.0, labelSize.height + 32.0)) } @@ -79,7 +79,7 @@ public class ActionSheetTextNode: ActionSheetItemNode { let size = self.bounds.size - let labelSize = self.label.measure(CGSize(width: max(1.0, size.width - 20.0), height: size.height)) + let labelSize = self.label.updateLayout(CGSize(width: max(1.0, size.width - 20.0), height: size.height)) self.label.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - labelSize.width) / 2.0), y: floorToScreenPixels((size.height - labelSize.height) / 2.0)), size: labelSize) self.accessibilityArea.frame = CGRect(origin: CGPoint(), size: size) diff --git a/submodules/Display/Display/ActionSheetTheme.swift b/submodules/Display/Source/ActionSheetTheme.swift similarity index 100% rename from submodules/Display/Display/ActionSheetTheme.swift rename to submodules/Display/Source/ActionSheetTheme.swift diff --git a/submodules/Display/Display/AlertContentNode.swift b/submodules/Display/Source/AlertContentNode.swift similarity index 100% rename from submodules/Display/Display/AlertContentNode.swift rename to submodules/Display/Source/AlertContentNode.swift diff --git a/submodules/Display/Display/AlertController.swift b/submodules/Display/Source/AlertController.swift similarity index 100% rename from submodules/Display/Display/AlertController.swift rename to submodules/Display/Source/AlertController.swift diff --git a/submodules/Display/Display/AlertControllerNode.swift b/submodules/Display/Source/AlertControllerNode.swift similarity index 100% rename from submodules/Display/Display/AlertControllerNode.swift rename to submodules/Display/Source/AlertControllerNode.swift diff --git a/submodules/Display/Display/CAAnimationUtils.swift b/submodules/Display/Source/CAAnimationUtils.swift similarity index 99% rename from submodules/Display/Display/CAAnimationUtils.swift rename to submodules/Display/Source/CAAnimationUtils.swift index 0a251ebae1..6eb000add6 100644 --- a/submodules/Display/Display/CAAnimationUtils.swift +++ b/submodules/Display/Source/CAAnimationUtils.swift @@ -1,4 +1,5 @@ import UIKit +import UIKitRuntimeUtils @objc private class CALayerAnimationDelegate: NSObject, CAAnimationDelegate { private let keyPath: String? diff --git a/submodules/Display/Display/ChildWindowHostView.swift b/submodules/Display/Source/ChildWindowHostView.swift similarity index 100% rename from submodules/Display/Display/ChildWindowHostView.swift rename to submodules/Display/Source/ChildWindowHostView.swift diff --git a/submodules/Display/Display/CollectionIndexNode.swift b/submodules/Display/Source/CollectionIndexNode.swift similarity index 100% rename from submodules/Display/Display/CollectionIndexNode.swift rename to submodules/Display/Source/CollectionIndexNode.swift diff --git a/submodules/Display/Display/ContainableController.swift b/submodules/Display/Source/ContainableController.swift similarity index 100% rename from submodules/Display/Display/ContainableController.swift rename to submodules/Display/Source/ContainableController.swift diff --git a/submodules/Display/Display/ContainedViewLayoutTransition.swift b/submodules/Display/Source/ContainedViewLayoutTransition.swift similarity index 100% rename from submodules/Display/Display/ContainedViewLayoutTransition.swift rename to submodules/Display/Source/ContainedViewLayoutTransition.swift diff --git a/submodules/Display/Display/ContainerViewLayout.swift b/submodules/Display/Source/ContainerViewLayout.swift similarity index 100% rename from submodules/Display/Display/ContainerViewLayout.swift rename to submodules/Display/Source/ContainerViewLayout.swift diff --git a/submodules/Display/Display/ContextContentContainerNode.swift b/submodules/Display/Source/ContextContentContainerNode.swift similarity index 100% rename from submodules/Display/Display/ContextContentContainerNode.swift rename to submodules/Display/Source/ContextContentContainerNode.swift diff --git a/submodules/Display/Display/ContextContentSourceNode.swift b/submodules/Display/Source/ContextContentSourceNode.swift similarity index 100% rename from submodules/Display/Display/ContextContentSourceNode.swift rename to submodules/Display/Source/ContextContentSourceNode.swift diff --git a/submodules/Display/Display/ContextControllerSourceNode.swift b/submodules/Display/Source/ContextControllerSourceNode.swift similarity index 100% rename from submodules/Display/Display/ContextControllerSourceNode.swift rename to submodules/Display/Source/ContextControllerSourceNode.swift diff --git a/submodules/Display/Display/ContextGesture.swift b/submodules/Display/Source/ContextGesture.swift similarity index 100% rename from submodules/Display/Display/ContextGesture.swift rename to submodules/Display/Source/ContextGesture.swift diff --git a/submodules/Display/Display/ContextMenuAction.swift b/submodules/Display/Source/ContextMenuAction.swift similarity index 100% rename from submodules/Display/Display/ContextMenuAction.swift rename to submodules/Display/Source/ContextMenuAction.swift diff --git a/submodules/Display/Display/ContextMenuActionNode.swift b/submodules/Display/Source/ContextMenuActionNode.swift similarity index 98% rename from submodules/Display/Display/ContextMenuActionNode.swift rename to submodules/Display/Source/ContextMenuActionNode.swift index a26ed60144..c626ff7e0c 100644 --- a/submodules/Display/Display/ContextMenuActionNode.swift +++ b/submodules/Display/Source/ContextMenuActionNode.swift @@ -41,7 +41,6 @@ final class ContextMenuActionNode: ASDisplayNode { case let .icon(image): let iconNode = ASImageNode() iconNode.displaysAsynchronously = false - iconNode.displayWithoutProcessing = true iconNode.image = image self.iconNode = iconNode diff --git a/submodules/Display/Display/ContextMenuContainerNode.swift b/submodules/Display/Source/ContextMenuContainerNode.swift similarity index 100% rename from submodules/Display/Display/ContextMenuContainerNode.swift rename to submodules/Display/Source/ContextMenuContainerNode.swift diff --git a/submodules/Display/Display/ContextMenuController.swift b/submodules/Display/Source/ContextMenuController.swift similarity index 100% rename from submodules/Display/Display/ContextMenuController.swift rename to submodules/Display/Source/ContextMenuController.swift diff --git a/submodules/Display/Display/ContextMenuNode.swift b/submodules/Display/Source/ContextMenuNode.swift similarity index 99% rename from submodules/Display/Display/ContextMenuNode.swift rename to submodules/Display/Source/ContextMenuNode.swift index 8da32a3d48..00e9eab0c1 100644 --- a/submodules/Display/Display/ContextMenuNode.swift +++ b/submodules/Display/Source/ContextMenuNode.swift @@ -28,11 +28,9 @@ private final class ContextMenuContentScrollNode: ASDisplayNode { let shadowImage = generateShadowImage() self.leftShadow = ASImageNode() - self.leftShadow.displayWithoutProcessing = true self.leftShadow.displaysAsynchronously = false self.leftShadow.image = shadowImage self.rightShadow = ASImageNode() - self.rightShadow.displayWithoutProcessing = true self.rightShadow.displaysAsynchronously = false self.rightShadow.image = shadowImage self.rightShadow.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0) diff --git a/submodules/Display/Display/DeviceMetrics.swift b/submodules/Display/Source/DeviceMetrics.swift similarity index 100% rename from submodules/Display/Display/DeviceMetrics.swift rename to submodules/Display/Source/DeviceMetrics.swift diff --git a/submodules/Display/Display/DisplayLinkAnimator.swift b/submodules/Display/Source/DisplayLinkAnimator.swift similarity index 100% rename from submodules/Display/Display/DisplayLinkAnimator.swift rename to submodules/Display/Source/DisplayLinkAnimator.swift diff --git a/submodules/Display/Display/DisplayLinkDispatcher.swift b/submodules/Display/Source/DisplayLinkDispatcher.swift similarity index 100% rename from submodules/Display/Display/DisplayLinkDispatcher.swift rename to submodules/Display/Source/DisplayLinkDispatcher.swift diff --git a/submodules/Display/Display/EditableTextNode.swift b/submodules/Display/Source/EditableTextNode.swift similarity index 100% rename from submodules/Display/Display/EditableTextNode.swift rename to submodules/Display/Source/EditableTextNode.swift diff --git a/submodules/Display/Display/Font.swift b/submodules/Display/Source/Font.swift similarity index 100% rename from submodules/Display/Display/Font.swift rename to submodules/Display/Source/Font.swift diff --git a/submodules/Display/Display/GenerateImage.swift b/submodules/Display/Source/GenerateImage.swift similarity index 95% rename from submodules/Display/Display/GenerateImage.swift rename to submodules/Display/Source/GenerateImage.swift index 4b7f38fbe9..fc8437bd5c 100644 --- a/submodules/Display/Display/GenerateImage.swift +++ b/submodules/Display/Source/GenerateImage.swift @@ -207,6 +207,24 @@ public func generateFilledCircleImage(diameter: CGFloat, color: UIColor?, stroke }) } +public func generateAdjustedStretchableFilledCircleImage(diameter: CGFloat, color: UIColor) -> UIImage? { + let corner: CGFloat = diameter / 2.0 + return generateImage(CGSize(width: diameter + 2.0, height: diameter + 2.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(color.cgColor) + context.move(to: CGPoint(x: 0.0, y: corner)) + context.addArc(tangent1End: CGPoint(x: 0.0, y: 0.0), tangent2End: CGPoint(x: corner, y: 0.0), radius: corner) + context.addLine(to: CGPoint(x: size.width - corner, y: 0.0)) + context.addArc(tangent1End: CGPoint(x: size.width, y: 0.0), tangent2End: CGPoint(x: size.width, y: corner), radius: corner) + context.addLine(to: CGPoint(x: size.width, y: size.height - corner)) + context.addArc(tangent1End: CGPoint(x: size.width, y: size.height), tangent2End: CGPoint(x: size.width - corner, y: size.height), radius: corner) + context.addLine(to: CGPoint(x: corner, y: size.height)) + context.addArc(tangent1End: CGPoint(x: 0.0, y: size.height), tangent2End: CGPoint(x: 0.0, y: size.height - corner), radius: corner) + context.closePath() + context.fillPath() + })?.stretchableImage(withLeftCapWidth: Int(diameter / 2) + 1, topCapHeight: Int(diameter / 2) + 1) +} + public func generateCircleImage(diameter: CGFloat, lineWidth: CGFloat, color: UIColor?, strokeColor: UIColor? = nil, strokeWidth: CGFloat? = nil, backgroundColor: UIColor? = nil) -> UIImage? { return generateImage(CGSize(width: diameter, height: diameter), contextGenerator: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) diff --git a/submodules/Display/Display/GlobalOverlayPresentationContext.swift b/submodules/Display/Source/GlobalOverlayPresentationContext.swift similarity index 99% rename from submodules/Display/Display/GlobalOverlayPresentationContext.swift rename to submodules/Display/Source/GlobalOverlayPresentationContext.swift index c30965ce97..127aa07ddd 100644 --- a/submodules/Display/Display/GlobalOverlayPresentationContext.swift +++ b/submodules/Display/Source/GlobalOverlayPresentationContext.swift @@ -150,7 +150,6 @@ final class GlobalOverlayPresentationContext { view.addSubview(controller.view) } (controller as? UIViewController)?.setIgnoreAppearanceMethodInvocations(false) - view.layer.invalidateUpTheTree() controller.viewWillAppear(false) controller.viewDidAppear(false) } diff --git a/submodules/Display/Display/GridItem.swift b/submodules/Display/Source/GridItem.swift similarity index 100% rename from submodules/Display/Display/GridItem.swift rename to submodules/Display/Source/GridItem.swift diff --git a/submodules/Display/Display/GridItemNode.swift b/submodules/Display/Source/GridItemNode.swift similarity index 100% rename from submodules/Display/Display/GridItemNode.swift rename to submodules/Display/Source/GridItemNode.swift diff --git a/submodules/Display/Display/GridNode.swift b/submodules/Display/Source/GridNode.swift similarity index 100% rename from submodules/Display/Display/GridNode.swift rename to submodules/Display/Source/GridNode.swift diff --git a/submodules/Display/Display/GridNodeScroller.swift b/submodules/Display/Source/GridNodeScroller.swift similarity index 100% rename from submodules/Display/Display/GridNodeScroller.swift rename to submodules/Display/Source/GridNodeScroller.swift diff --git a/submodules/Display/Display/HapticFeedback.swift b/submodules/Display/Source/HapticFeedback.swift similarity index 100% rename from submodules/Display/Display/HapticFeedback.swift rename to submodules/Display/Source/HapticFeedback.swift diff --git a/submodules/Display/Display/HighlightTrackingButton.swift b/submodules/Display/Source/HighlightTrackingButton.swift similarity index 100% rename from submodules/Display/Display/HighlightTrackingButton.swift rename to submodules/Display/Source/HighlightTrackingButton.swift diff --git a/submodules/Display/Display/HighlightableButton.swift b/submodules/Display/Source/HighlightableButton.swift similarity index 100% rename from submodules/Display/Display/HighlightableButton.swift rename to submodules/Display/Source/HighlightableButton.swift diff --git a/submodules/Display/Display/ImageCorners.swift b/submodules/Display/Source/ImageCorners.swift similarity index 100% rename from submodules/Display/Display/ImageCorners.swift rename to submodules/Display/Source/ImageCorners.swift diff --git a/submodules/Display/Display/ImageNode.swift b/submodules/Display/Source/ImageNode.swift similarity index 100% rename from submodules/Display/Display/ImageNode.swift rename to submodules/Display/Source/ImageNode.swift diff --git a/submodules/Display/Display/ImmediateTextNode.swift b/submodules/Display/Source/ImmediateTextNode.swift similarity index 83% rename from submodules/Display/Display/ImmediateTextNode.swift rename to submodules/Display/Source/ImmediateTextNode.swift index f7e8dfc340..75116832f0 100644 --- a/submodules/Display/Display/ImmediateTextNode.swift +++ b/submodules/Display/Source/ImmediateTextNode.swift @@ -16,6 +16,32 @@ public class ImmediateTextNode: TextNode { public var textShadowColor: UIColor? public var textStroke: (UIColor, CGFloat)? public var cutout: TextNodeCutout? + + public var truncationMode: NSLineBreakMode { + get { + switch self.truncationType { + case .start: + return .byTruncatingHead + case .middle: + return .byTruncatingMiddle + case .end: + return .byTruncatingTail + @unknown default: + return .byTruncatingTail + } + } set(value) { + switch value { + case .byTruncatingHead: + self.truncationType = .start + case .byTruncatingMiddle: + self.truncationType = .middle + case .byTruncatingTail: + self.truncationType = .end + default: + self.truncationType = .end + } + } + } private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer? private var linkHighlightingNode: LinkHighlightingNode? @@ -24,6 +50,8 @@ public class ImmediateTextNode: TextNode { public var trailingLineWidth: CGFloat? + var constrainedSize: CGSize? + public var highlightAttributeAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)? { didSet { if self.isNodeLoaded { @@ -57,6 +85,8 @@ public class ImmediateTextNode: TextNode { } public func updateLayout(_ constrainedSize: CGSize) -> CGSize { + self.constrainedSize = constrainedSize + let makeLayout = TextNode.asyncLayout(self) let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: self.maximumNumberOfLines, truncationType: self.truncationType, constrainedSize: constrainedSize, alignment: self.textAlignment, lineSpacing: self.lineSpacing, cutout: self.cutout, insets: self.insets, textShadowColor: self.textShadowColor, textStroke: self.textStroke)) let _ = apply() @@ -69,12 +99,20 @@ public class ImmediateTextNode: TextNode { } public func updateLayoutInfo(_ constrainedSize: CGSize) -> ImmediateTextNodeLayoutInfo { + self.constrainedSize = constrainedSize + let makeLayout = TextNode.asyncLayout(self) let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: self.maximumNumberOfLines, truncationType: self.truncationType, constrainedSize: constrainedSize, alignment: self.textAlignment, lineSpacing: self.lineSpacing, cutout: self.cutout, insets: self.insets)) let _ = apply() return ImmediateTextNodeLayoutInfo(size: layout.size, truncated: layout.truncated) } + public func redrawIfPossible() { + if let constrainedSize = self.constrainedSize { + let _ = self.updateLayout(constrainedSize) + } + } + override public func didLoad() { super.didLoad() @@ -157,3 +195,21 @@ public class ImmediateTextNode: TextNode { } } } + +public class ASTextNode: ImmediateTextNode { + override public var attributedText: NSAttributedString? { + didSet { + self.setNeedsLayout() + } + } + + override public init() { + super.init() + + self.maximumNumberOfLines = 0 + } + + override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + return self.updateLayout(constrainedSize) + } +} diff --git a/submodules/Display/Source/InteractiveTransitionGestureRecognizer.swift b/submodules/Display/Source/InteractiveTransitionGestureRecognizer.swift new file mode 100644 index 0000000000..48761f12a1 --- /dev/null +++ b/submodules/Display/Source/InteractiveTransitionGestureRecognizer.swift @@ -0,0 +1,168 @@ +import Foundation +import UIKit + +private enum HorizontalGestures { + case none + case some + case strict +} + +private func hasHorizontalGestures(_ view: UIView, point: CGPoint?) -> HorizontalGestures { + if let disablesInteractiveTransitionGestureRecognizerNow = view.disablesInteractiveTransitionGestureRecognizerNow, disablesInteractiveTransitionGestureRecognizerNow() { + return .strict + } + + if view.disablesInteractiveTransitionGestureRecognizer { + return .some + } + + if let point = point, let test = view.interactiveTransitionGestureRecognizerTest, test(point) { + return .some + } + + if let view = view as? ListViewBackingView { + let transform = view.transform + let angle: Double = Double(atan2f(Float(transform.b), Float(transform.a))) + let term1: Double = abs(angle - Double.pi / 2.0) + let term2: Double = abs(angle + Double.pi / 2.0) + let term3: Double = abs(angle - Double.pi * 3.0 / 2.0) + if term1 < 0.001 || term2 < 0.001 || term3 < 0.001 { + return .some + } + } + + if let superview = view.superview { + return hasHorizontalGestures(superview, point: point != nil ? view.convert(point!, to: superview) : nil) + } else { + return .none + } +} + +public struct InteractiveTransitionGestureRecognizerDirections: OptionSet { + public var rawValue: Int + + public init(rawValue: Int) { + self.rawValue = rawValue + } + + public static let leftEdge = InteractiveTransitionGestureRecognizerDirections(rawValue: 1 << 2) + public static let rightEdge = InteractiveTransitionGestureRecognizerDirections(rawValue: 1 << 3) + public static let leftCenter = InteractiveTransitionGestureRecognizerDirections(rawValue: 1 << 0) + public static let rightCenter = InteractiveTransitionGestureRecognizerDirections(rawValue: 1 << 1) + + public static let left: InteractiveTransitionGestureRecognizerDirections = [.leftEdge, .leftCenter] + public static let right: InteractiveTransitionGestureRecognizerDirections = [.rightEdge, .rightCenter] +} + +public enum InteractiveTransitionGestureRecognizerEdgeWidth { + case constant(CGFloat) + case widthMultiplier(factor: CGFloat, min: CGFloat, max: CGFloat) +} + +public class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer { + private let edgeWidth: InteractiveTransitionGestureRecognizerEdgeWidth + private let allowedDirections: (CGPoint) -> InteractiveTransitionGestureRecognizerDirections + + private var validatedGesture = false + private var firstLocation: CGPoint = CGPoint() + private var currentAllowedDirections: InteractiveTransitionGestureRecognizerDirections = [] + + public init(target: Any?, action: Selector?, allowedDirections: @escaping (CGPoint) -> InteractiveTransitionGestureRecognizerDirections, edgeWidth: InteractiveTransitionGestureRecognizerEdgeWidth = .constant(16.0)) { + self.allowedDirections = allowedDirections + self.edgeWidth = edgeWidth + + super.init(target: target, action: action) + + self.maximumNumberOfTouches = 1 + } + + override public func reset() { + super.reset() + + self.validatedGesture = false + self.currentAllowedDirections = [] + } + + override public func touchesBegan(_ touches: Set, with event: UIEvent) { + let touch = touches.first! + let point = touch.location(in: self.view) + + var allowedDirections = self.allowedDirections(point) + if allowedDirections.isEmpty { + self.state = .failed + return + } + + super.touchesBegan(touches, with: event) + + self.firstLocation = point + + if let target = self.view?.hitTest(self.firstLocation, with: event) { + let horizontalGestures = hasHorizontalGestures(target, point: self.view?.convert(self.firstLocation, to: target)) + switch horizontalGestures { + case .some, .strict: + if case .strict = horizontalGestures { + allowedDirections = [] + } else if allowedDirections.contains(.leftEdge) || allowedDirections.contains(.rightEdge) { + allowedDirections.remove(.leftCenter) + allowedDirections.remove(.rightCenter) + } + case .none: + break + } + } + + if allowedDirections.isEmpty { + self.state = .failed + } else { + self.currentAllowedDirections = allowedDirections + } + } + + override public func touchesMoved(_ touches: Set, with event: UIEvent) { + let location = touches.first!.location(in: self.view) + let translation = CGPoint(x: location.x - self.firstLocation.x, y: location.y - self.firstLocation.y) + + let absTranslationX: CGFloat = abs(translation.x) + let absTranslationY: CGFloat = abs(translation.y) + + let size = self.view?.bounds.size ?? CGSize() + + let edgeWidth: CGFloat + switch self.edgeWidth { + case let .constant(value): + edgeWidth = value + case let .widthMultiplier(factor, minValue, maxValue): + edgeWidth = max(minValue, min(size.width * factor, maxValue)) + } + + if !self.validatedGesture { + if self.firstLocation.x < edgeWidth && !self.currentAllowedDirections.contains(.rightEdge) { + self.state = .failed + return + } + if self.firstLocation.x > size.width - edgeWidth && !self.currentAllowedDirections.contains(.leftEdge) { + self.state = .failed + return + } + + if self.currentAllowedDirections.contains(.rightEdge) && self.firstLocation.x < edgeWidth { + self.validatedGesture = true + } else if self.currentAllowedDirections.contains(.leftEdge) && self.firstLocation.x > size.width - edgeWidth { + self.validatedGesture = true + } else if !self.currentAllowedDirections.contains(.leftCenter) && translation.x < 0.0 { + self.state = .failed + } else if !self.currentAllowedDirections.contains(.rightCenter) && translation.x > 0.0 { + self.state = .failed + } else if absTranslationY > 2.0 && absTranslationY > absTranslationX * 2.0 { + self.state = .failed + } else if absTranslationX > 2.0 && absTranslationY * 2.0 < absTranslationX { + self.validatedGesture = true + } + } + + if self.validatedGesture { + super.touchesMoved(touches, with: event) + } + } +} diff --git a/submodules/Display/Display/KeyShortcut.swift b/submodules/Display/Source/KeyShortcut.swift similarity index 100% rename from submodules/Display/Display/KeyShortcut.swift rename to submodules/Display/Source/KeyShortcut.swift diff --git a/submodules/Display/Display/KeyShortcutsController.swift b/submodules/Display/Source/KeyShortcutsController.swift similarity index 100% rename from submodules/Display/Display/KeyShortcutsController.swift rename to submodules/Display/Source/KeyShortcutsController.swift diff --git a/submodules/Display/Display/Keyboard.swift b/submodules/Display/Source/Keyboard.swift similarity index 84% rename from submodules/Display/Display/Keyboard.swift rename to submodules/Display/Source/Keyboard.swift index 912cd8fb44..53c3b5d048 100644 --- a/submodules/Display/Display/Keyboard.swift +++ b/submodules/Display/Source/Keyboard.swift @@ -1,4 +1,5 @@ import Foundation +import UIKitRuntimeUtils public enum Keyboard { public static func applyAutocorrection() { diff --git a/submodules/Display/Display/KeyboardManager.swift b/submodules/Display/Source/KeyboardManager.swift similarity index 82% rename from submodules/Display/Display/KeyboardManager.swift rename to submodules/Display/Source/KeyboardManager.swift index 67e903a8e0..89d4de9ffe 100644 --- a/submodules/Display/Display/KeyboardManager.swift +++ b/submodules/Display/Source/KeyboardManager.swift @@ -1,6 +1,7 @@ import Foundation import UIKit import AsyncDisplayKit +import UIKitRuntimeUtils struct KeyboardSurface { let host: UIView @@ -22,7 +23,6 @@ private func getFirstResponder(_ view: UIView) -> UIView? { class KeyboardManager { private let host: StatusBarHost - private weak var previousPositionAnimationMirrorSource: CATracingLayer? private weak var previousFirstResponderView: UIView? private var interactiveInputOffset: CGFloat = 0.0 @@ -95,22 +95,8 @@ class KeyboardManager { //print("set to \(CGPoint(x: containerOrigin.x, y: self.interactiveInputOffset))") keyboardWindow.layer.sublayerTransform = horizontalTranslation } - if let tracingLayer = firstResponderView.layer as? CATracingLayer, firstResponderDisableAutomaticKeyboardHandling.isEmpty { - if let previousPositionAnimationMirrorSource = self.previousPositionAnimationMirrorSource, previousPositionAnimationMirrorSource !== tracingLayer { - previousPositionAnimationMirrorSource.setPositionAnimationMirrorTarget(nil) - } - tracingLayer.setPositionAnimationMirrorTarget(keyboardWindow.layer) - self.previousPositionAnimationMirrorSource = tracingLayer - } else if let previousPositionAnimationMirrorSource = self.previousPositionAnimationMirrorSource { - previousPositionAnimationMirrorSource.setPositionAnimationMirrorTarget(nil) - self.previousPositionAnimationMirrorSource = nil - } } else { keyboardWindow.layer.sublayerTransform = CATransform3DIdentity - if let previousPositionAnimationMirrorSource = self.previousPositionAnimationMirrorSource { - previousPositionAnimationMirrorSource.setPositionAnimationMirrorTarget(nil) - self.previousPositionAnimationMirrorSource = nil - } if let previousFirstResponderView = previousFirstResponderView { if previousFirstResponderView.window == nil { keyboardWindow.isHidden = true diff --git a/submodules/Display/Display/LayoutSizes.swift b/submodules/Display/Source/LayoutSizes.swift similarity index 100% rename from submodules/Display/Display/LayoutSizes.swift rename to submodules/Display/Source/LayoutSizes.swift diff --git a/submodules/Display/Display/LegacyPresentedController.swift b/submodules/Display/Source/LegacyPresentedController.swift similarity index 100% rename from submodules/Display/Display/LegacyPresentedController.swift rename to submodules/Display/Source/LegacyPresentedController.swift diff --git a/submodules/Display/Display/LegacyPresentedControllerNode.swift b/submodules/Display/Source/LegacyPresentedControllerNode.swift similarity index 100% rename from submodules/Display/Display/LegacyPresentedControllerNode.swift rename to submodules/Display/Source/LegacyPresentedControllerNode.swift diff --git a/submodules/Display/Display/LinkHighlightingNode.swift b/submodules/Display/Source/LinkHighlightingNode.swift similarity index 99% rename from submodules/Display/Display/LinkHighlightingNode.swift rename to submodules/Display/Source/LinkHighlightingNode.swift index 611616965e..26efc7afff 100644 --- a/submodules/Display/Display/LinkHighlightingNode.swift +++ b/submodules/Display/Source/LinkHighlightingNode.swift @@ -181,7 +181,6 @@ public final class LinkHighlightingNode: ASDisplayNode { self.imageNode = ASImageNode() self.imageNode.isUserInteractionEnabled = false self.imageNode.displaysAsynchronously = false - self.imageNode.displayWithoutProcessing = true super.init() diff --git a/submodules/Display/Display/ListView.swift b/submodules/Display/Source/ListView.swift similarity index 99% rename from submodules/Display/Display/ListView.swift rename to submodules/Display/Source/ListView.swift index 0e098f0d79..33ecb3d5e3 100644 --- a/submodules/Display/Display/ListView.swift +++ b/submodules/Display/Source/ListView.swift @@ -1,6 +1,7 @@ import UIKit import AsyncDisplayKit import SwiftSignalKit +import UIKitRuntimeUtils private let infiniteScrollSize: CGFloat = 10000.0 private let insertionAnimationDuration: Double = 0.4 @@ -989,8 +990,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } } - if let keepMinimalScrollHeightWithTopInset = self.keepMinimalScrollHeightWithTopInset { - completeHeight = max(completeHeight, self.visibleSize.height + keepMinimalScrollHeightWithTopInset) + if let keepMinimalScrollHeightWithTopInset = self.keepMinimalScrollHeightWithTopInset, topItemFound { + completeHeight = max(completeHeight, self.visibleSize.height + keepMinimalScrollHeightWithTopInset - effectiveInsets.bottom - effectiveInsets.top) bottomItemEdge = max(bottomItemEdge, topItemEdge + completeHeight) } diff --git a/submodules/Display/Display/ListViewAccessoryItem.swift b/submodules/Display/Source/ListViewAccessoryItem.swift similarity index 100% rename from submodules/Display/Display/ListViewAccessoryItem.swift rename to submodules/Display/Source/ListViewAccessoryItem.swift diff --git a/submodules/Display/Display/ListViewAccessoryItemNode.swift b/submodules/Display/Source/ListViewAccessoryItemNode.swift similarity index 100% rename from submodules/Display/Display/ListViewAccessoryItemNode.swift rename to submodules/Display/Source/ListViewAccessoryItemNode.swift diff --git a/submodules/Display/Display/ListViewAnimation.swift b/submodules/Display/Source/ListViewAnimation.swift similarity index 99% rename from submodules/Display/Display/ListViewAnimation.swift rename to submodules/Display/Source/ListViewAnimation.swift index fa432d852d..57dc0d4b2e 100644 --- a/submodules/Display/Display/ListViewAnimation.swift +++ b/submodules/Display/Source/ListViewAnimation.swift @@ -1,5 +1,6 @@ import Foundation import UIKit +import UIKitRuntimeUtils public protocol Interpolatable { static func interpolator() -> (Interpolatable, Interpolatable, CGFloat) -> (Interpolatable) diff --git a/submodules/Display/Display/ListViewFloatingHeaderNode.swift b/submodules/Display/Source/ListViewFloatingHeaderNode.swift similarity index 100% rename from submodules/Display/Display/ListViewFloatingHeaderNode.swift rename to submodules/Display/Source/ListViewFloatingHeaderNode.swift diff --git a/submodules/Display/Display/ListViewIntermediateState.swift b/submodules/Display/Source/ListViewIntermediateState.swift similarity index 100% rename from submodules/Display/Display/ListViewIntermediateState.swift rename to submodules/Display/Source/ListViewIntermediateState.swift diff --git a/submodules/Display/Display/ListViewItem.swift b/submodules/Display/Source/ListViewItem.swift similarity index 100% rename from submodules/Display/Display/ListViewItem.swift rename to submodules/Display/Source/ListViewItem.swift diff --git a/submodules/Display/Display/ListViewItemHeader.swift b/submodules/Display/Source/ListViewItemHeader.swift similarity index 91% rename from submodules/Display/Display/ListViewItemHeader.swift rename to submodules/Display/Source/ListViewItemHeader.swift index fb288b3187..e81635ae8f 100644 --- a/submodules/Display/Display/ListViewItemHeader.swift +++ b/submodules/Display/Source/ListViewItemHeader.swift @@ -50,24 +50,9 @@ open class ListViewItemHeaderNode: ASDisplayNode { self.spring = ListViewItemSpring(stiffness: -280.0, damping: -24.0, mass: 0.85) } - if seeThrough { - if (layerBacked) { - super.init() - self.setLayerBlock({ - return CASeeThroughTracingLayer() - }) - } else { - super.init() - - self.setViewBlock({ - return CASeeThroughTracingView() - }) - } - } else { - super.init() + super.init() - self.isLayerBacked = layerBacked - } + self.isLayerBacked = layerBacked } open func updateStickDistanceFactor(_ factor: CGFloat, transition: ContainedViewLayoutTransition) { diff --git a/submodules/Display/Display/ListViewItemNode.swift b/submodules/Display/Source/ListViewItemNode.swift similarity index 97% rename from submodules/Display/Display/ListViewItemNode.swift rename to submodules/Display/Source/ListViewItemNode.swift index 9ef0d00b25..1f41a35689 100644 --- a/submodules/Display/Display/ListViewItemNode.swift +++ b/submodules/Display/Source/ListViewItemNode.swift @@ -211,24 +211,9 @@ open class ListViewItemNode: ASDisplayNode { self.rotated = rotated - if seeThrough { - if (layerBacked) { - super.init() - self.setLayerBlock({ - return CASeeThroughTracingLayer() - }) - } else { - super.init() + super.init() - self.setViewBlock({ - return CASeeThroughTracingView() - }) - } - } else { - super.init() - - self.isLayerBacked = layerBacked - } + self.isLayerBacked = layerBacked } var apparentHeight: CGFloat = 0.0 diff --git a/submodules/Display/Display/ListViewOverscrollBackgroundNode.swift b/submodules/Display/Source/ListViewOverscrollBackgroundNode.swift similarity index 100% rename from submodules/Display/Display/ListViewOverscrollBackgroundNode.swift rename to submodules/Display/Source/ListViewOverscrollBackgroundNode.swift diff --git a/submodules/Display/Display/ListViewReorderingGestureRecognizer.swift b/submodules/Display/Source/ListViewReorderingGestureRecognizer.swift similarity index 100% rename from submodules/Display/Display/ListViewReorderingGestureRecognizer.swift rename to submodules/Display/Source/ListViewReorderingGestureRecognizer.swift diff --git a/submodules/Display/Display/ListViewReorderingItemNode.swift b/submodules/Display/Source/ListViewReorderingItemNode.swift similarity index 100% rename from submodules/Display/Display/ListViewReorderingItemNode.swift rename to submodules/Display/Source/ListViewReorderingItemNode.swift diff --git a/submodules/Display/Display/ListViewScroller.swift b/submodules/Display/Source/ListViewScroller.swift similarity index 100% rename from submodules/Display/Display/ListViewScroller.swift rename to submodules/Display/Source/ListViewScroller.swift diff --git a/submodules/Display/Display/ListViewTapGestureRecognizer.swift b/submodules/Display/Source/ListViewTapGestureRecognizer.swift similarity index 100% rename from submodules/Display/Display/ListViewTapGestureRecognizer.swift rename to submodules/Display/Source/ListViewTapGestureRecognizer.swift diff --git a/submodules/Display/Display/ListViewTempItemNode.swift b/submodules/Display/Source/ListViewTempItemNode.swift similarity index 100% rename from submodules/Display/Display/ListViewTempItemNode.swift rename to submodules/Display/Source/ListViewTempItemNode.swift diff --git a/submodules/Display/Display/ListViewTransactionQueue.swift b/submodules/Display/Source/ListViewTransactionQueue.swift similarity index 100% rename from submodules/Display/Display/ListViewTransactionQueue.swift rename to submodules/Display/Source/ListViewTransactionQueue.swift diff --git a/submodules/Display/Display/MinimizeKeyboardGestureRecognizer.swift b/submodules/Display/Source/MinimizeKeyboardGestureRecognizer.swift similarity index 100% rename from submodules/Display/Display/MinimizeKeyboardGestureRecognizer.swift rename to submodules/Display/Source/MinimizeKeyboardGestureRecognizer.swift diff --git a/submodules/Display/Display/NativeWindowHostView.swift b/submodules/Display/Source/NativeWindowHostView.swift similarity index 100% rename from submodules/Display/Display/NativeWindowHostView.swift rename to submodules/Display/Source/NativeWindowHostView.swift diff --git a/submodules/Display/Display/Navigation/NavigationContainer.swift b/submodules/Display/Source/Navigation/NavigationContainer.swift similarity index 99% rename from submodules/Display/Display/Navigation/NavigationContainer.swift rename to submodules/Display/Source/Navigation/NavigationContainer.swift index c45e446656..b5be8762d8 100644 --- a/submodules/Display/Display/Navigation/NavigationContainer.swift +++ b/submodules/Display/Source/Navigation/NavigationContainer.swift @@ -111,7 +111,7 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate { override func didLoad() { super.didLoad() - let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] in + let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] _ in guard let strongSelf = self, strongSelf.controllers.count > 1 else { return [] } diff --git a/submodules/Display/Display/Navigation/NavigationController.swift b/submodules/Display/Source/Navigation/NavigationController.swift similarity index 99% rename from submodules/Display/Display/Navigation/NavigationController.swift rename to submodules/Display/Source/Navigation/NavigationController.swift index 4278c6c275..1240f957d3 100644 --- a/submodules/Display/Display/Navigation/NavigationController.swift +++ b/submodules/Display/Source/Navigation/NavigationController.swift @@ -35,9 +35,6 @@ public struct NavigationAnimationOptions : OptionSet { } private final class NavigationControllerContainerView: UIView { - override class var layerClass: AnyClass { - return CATracingLayer.self - } } public enum NavigationEmptyDetailsBackgoundMode { @@ -423,14 +420,14 @@ open class NavigationController: UINavigationController, ContainableController, self.modalContainers.append(modalContainer) if !modalContainer.isReady { modalContainer.isReadyUpdated = { [weak self, weak modalContainer] in - guard let strongSelf = self, let modalContainer = modalContainer else { + guard let strongSelf = self, let _ = modalContainer else { return } strongSelf.updateContainersNonReentrant(transition: .animated(duration: 0.5, curve: .spring)) } } modalContainer.updateDismissProgress = { [weak self, weak modalContainer] _, transition in - guard let strongSelf = self, let modalContainer = modalContainer else { + guard let strongSelf = self, let _ = modalContainer else { return } strongSelf.updateContainersNonReentrant(transition: transition) @@ -457,7 +454,7 @@ open class NavigationController: UINavigationController, ContainableController, container.view.endEditing(true) } - transition = container.dismiss(transition: transition, completion: { [weak self, weak container] in + transition = container.dismiss(transition: transition, completion: { [weak container] in container?.removeFromSupernode() }) } diff --git a/submodules/Display/Display/Navigation/NavigationLayout.swift b/submodules/Display/Source/Navigation/NavigationLayout.swift similarity index 100% rename from submodules/Display/Display/Navigation/NavigationLayout.swift rename to submodules/Display/Source/Navigation/NavigationLayout.swift diff --git a/submodules/Display/Display/Navigation/NavigationModalContainer.swift b/submodules/Display/Source/Navigation/NavigationModalContainer.swift similarity index 98% rename from submodules/Display/Display/Navigation/NavigationModalContainer.swift rename to submodules/Display/Source/Navigation/NavigationModalContainer.swift index 1798a9c436..aa40c78ee3 100644 --- a/submodules/Display/Display/Navigation/NavigationModalContainer.swift +++ b/submodules/Display/Source/Navigation/NavigationModalContainer.swift @@ -2,6 +2,7 @@ import Foundation import UIKit import AsyncDisplayKit import SwiftSignalKit +import UIKitRuntimeUtils final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDelegate { private var theme: NavigationControllerTheme @@ -90,7 +91,7 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes self.scrollNode.view.clipsToBounds = false self.scrollNode.view.delegate = self - let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] in + let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] _ in guard let strongSelf = self, !strongSelf.isDismissed else { return [] } @@ -432,6 +433,12 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut) alphaTransition.updateAlpha(node: self.dim, alpha: 0.0, beginWithCurrentState: true) positionTransition.updatePosition(node: self.container, position: CGPoint(x: self.container.position.x, y: self.bounds.height + self.container.bounds.height / 2.0 + self.bounds.height), beginWithCurrentState: true, completion: { [weak self] _ in + guard let strongSelf = self else { + return + } + for controller in strongSelf.container.controllers { + controller.viewDidDisappear(transition.isAnimated) + } completion() }) return positionTransition diff --git a/submodules/Display/Display/Navigation/NavigationModalFrame.swift b/submodules/Display/Source/Navigation/NavigationModalFrame.swift similarity index 96% rename from submodules/Display/Display/Navigation/NavigationModalFrame.swift rename to submodules/Display/Source/Navigation/NavigationModalFrame.swift index b40bec751f..023d255262 100644 --- a/submodules/Display/Display/Navigation/NavigationModalFrame.swift +++ b/submodules/Display/Source/Navigation/NavigationModalFrame.swift @@ -62,16 +62,12 @@ final class NavigationModalFrame: ASDisplayNode { self.topLeftCorner = ASImageNode() self.topLeftCorner.displaysAsynchronously = false - self.topLeftCorner.displayWithoutProcessing = true self.topRightCorner = ASImageNode() self.topRightCorner.displaysAsynchronously = false - self.topRightCorner.displayWithoutProcessing = true self.bottomLeftCorner = ASImageNode() self.bottomLeftCorner.displaysAsynchronously = false - self.bottomLeftCorner.displayWithoutProcessing = true self.bottomRightCorner = ASImageNode() self.bottomRightCorner.displaysAsynchronously = false - self.bottomRightCorner.displayWithoutProcessing = true super.init() diff --git a/submodules/Display/Display/Navigation/NavigationOverlayContainer.swift b/submodules/Display/Source/Navigation/NavigationOverlayContainer.swift similarity index 100% rename from submodules/Display/Display/Navigation/NavigationOverlayContainer.swift rename to submodules/Display/Source/Navigation/NavigationOverlayContainer.swift diff --git a/submodules/Display/Display/Navigation/NavigationSplitContainer.swift b/submodules/Display/Source/Navigation/NavigationSplitContainer.swift similarity index 100% rename from submodules/Display/Display/Navigation/NavigationSplitContainer.swift rename to submodules/Display/Source/Navigation/NavigationSplitContainer.swift diff --git a/submodules/Display/Display/NavigationBackButtonNode.swift b/submodules/Display/Source/NavigationBackButtonNode.swift similarity index 95% rename from submodules/Display/Display/NavigationBackButtonNode.swift rename to submodules/Display/Source/NavigationBackButtonNode.swift index 2aa17d953e..b6082776f2 100644 --- a/submodules/Display/Display/NavigationBackButtonNode.swift +++ b/submodules/Display/Source/NavigationBackButtonNode.swift @@ -15,7 +15,7 @@ public class NavigationBackButtonNode: ASControlNode { } let arrow: ASDisplayNode - let label: ASTextNode + let label: ImmediateTextNode private let arrowSpacing: CGFloat = 4.0 @@ -48,7 +48,7 @@ public class NavigationBackButtonNode: ASControlNode { override public init() { self.arrow = ASDisplayNode() - self.label = ASTextNode() + self.label = ImmediateTextNode() super.init() @@ -69,7 +69,7 @@ public class NavigationBackButtonNode: ASControlNode { } public override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { - self.label.measure(CGSize(width: max(0.0, constrainedSize.width - self.arrow.frame.size.width - self.arrowSpacing), height: constrainedSize.height)) + self.label.updateLayout(CGSize(width: max(0.0, constrainedSize.width - self.arrow.frame.size.width - self.arrowSpacing), height: constrainedSize.height)) return CGSize(width: self.arrow.frame.size.width + self.arrowSpacing + self.label.calculatedSize.width, height: max(self.arrow.frame.size.height, self.label.calculatedSize.height)) } diff --git a/submodules/Display/Display/NavigationBar.swift b/submodules/Display/Source/NavigationBar.swift similarity index 98% rename from submodules/Display/Display/NavigationBar.swift rename to submodules/Display/Source/NavigationBar.swift index 4c2039be42..8987abd847 100644 --- a/submodules/Display/Display/NavigationBar.swift +++ b/submodules/Display/Source/NavigationBar.swift @@ -288,7 +288,7 @@ open class NavigationBar: ASDisplayNode { public var layoutSuspended: Bool = false - private let titleNode: ASTextNode + private let titleNode: ImmediateTextNode var previousItemListenerKey: Int? var previousItemBackListenerKey: Int? @@ -685,7 +685,7 @@ open class NavigationBar: ASDisplayNode { self.presentationData = presentationData self.stripeNode = ASDisplayNode() - self.titleNode = ASTextNode() + self.titleNode = ImmediateTextNode() self.titleNode.isAccessibilityElement = true self.titleNode.accessibilityTraits = .header @@ -729,7 +729,7 @@ open class NavigationBar: ASDisplayNode { self.titleNode.displaysAsynchronously = false self.titleNode.maximumNumberOfLines = 1 - self.titleNode.truncationMode = .byTruncatingTail + self.titleNode.truncationType = .end self.titleNode.isOpaque = false self.backButtonNode.highlightChanged = { [weak self] index, highlighted in @@ -793,12 +793,16 @@ open class NavigationBar: ASDisplayNode { self.stripeNode.backgroundColor = self.presentationData.theme.separatorColor self.badgeNode.updateTheme(fillColor: self.presentationData.theme.badgeBackgroundColor, strokeColor: self.presentationData.theme.badgeStrokeColor, textColor: self.presentationData.theme.badgeTextColor) + + self.requestLayout() } } private func requestLayout() { - self.requestedLayout = true - self.setNeedsLayout() + if self.transitionState == nil { + self.requestedLayout = true + self.setNeedsLayout() + } } override open func layout() { @@ -869,8 +873,14 @@ open class NavigationBar: ASDisplayNode { let initialX: CGFloat = backButtonInset let finalX: CGFloat = floor((size.width - backButtonSize.width) / 2.0) - size.width - self.backButtonNode.frame = CGRect(origin: CGPoint(x: initialX * (1.0 - progress) + finalX * progress, y: contentVerticalOrigin + floor((nominalHeight - backButtonSize.height) / 2.0)), size: backButtonSize) - self.backButtonNode.alpha = (1.0 - progress) * (1.0 - progress) + let backButtonFrame = CGRect(origin: CGPoint(x: initialX * (1.0 - progress) + finalX * progress, y: contentVerticalOrigin + floor((nominalHeight - backButtonSize.height) / 2.0)), size: backButtonSize) + if self.backButtonNode.frame != backButtonFrame { + self.backButtonNode.frame = backButtonFrame + } + let backButtonAlpha = self.backButtonNode.alpha + if self.backButtonNode.alpha != backButtonAlpha { + self.backButtonNode.alpha = backButtonAlpha + } if let transitionTitleNode = self.transitionTitleNode { let transitionTitleSize = transitionTitleNode.measure(CGSize(width: size.width, height: nominalHeight)) @@ -955,7 +965,7 @@ open class NavigationBar: ASDisplayNode { } if self.titleNode.supernode != nil { - let titleSize = self.titleNode.measure(CGSize(width: max(1.0, size.width - max(leftTitleInset, rightTitleInset) * 2.0), height: nominalHeight)) + let titleSize = self.titleNode.updateLayout(CGSize(width: max(1.0, size.width - max(leftTitleInset, rightTitleInset) * 2.0), height: nominalHeight)) if let transitionState = self.transitionState, let otherNavigationBar = transitionState.navigationBar { let progress = transitionState.progress @@ -1037,7 +1047,7 @@ open class NavigationBar: ASDisplayNode { return nil } } else if let title = self.title { - let node = ASTextNode() + let node = ImmediateTextNode() node.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: foregroundColor) return node } else { diff --git a/submodules/Display/Display/NavigationBarBadge.swift b/submodules/Display/Source/NavigationBarBadge.swift similarity index 92% rename from submodules/Display/Display/NavigationBarBadge.swift rename to submodules/Display/Source/NavigationBarBadge.swift index 348ff085c5..333bcfb825 100644 --- a/submodules/Display/Display/NavigationBarBadge.swift +++ b/submodules/Display/Source/NavigationBarBadge.swift @@ -7,7 +7,7 @@ public final class NavigationBarBadgeNode: ASDisplayNode { private var strokeColor: UIColor private var textColor: UIColor - private let textNode: ASTextNode + private let textNode: ImmediateTextNode private let backgroundNode: ASImageNode private let font: UIFont = Font.regular(13.0) @@ -24,13 +24,12 @@ public final class NavigationBarBadgeNode: ASDisplayNode { self.strokeColor = strokeColor self.textColor = textColor - self.textNode = ASTextNode() + self.textNode = ImmediateTextNode() self.textNode.isUserInteractionEnabled = false self.textNode.displaysAsynchronously = false self.backgroundNode = ASImageNode() self.backgroundNode.isLayerBacked = true - self.backgroundNode.displayWithoutProcessing = true self.backgroundNode.displaysAsynchronously = false self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: fillColor, strokeColor: strokeColor, strokeWidth: 1.0) @@ -49,7 +48,7 @@ public final class NavigationBarBadgeNode: ASDisplayNode { } override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { - let badgeSize = self.textNode.measure(constrainedSize) + let badgeSize = self.textNode.updateLayout(constrainedSize) let backgroundSize = CGSize(width: max(18.0, badgeSize.width + 10.0 + 1.0), height: 18.0) let backgroundFrame = CGRect(origin: CGPoint(), size: backgroundSize) self.backgroundNode.frame = backgroundFrame diff --git a/submodules/Display/Display/NavigationBarContentNode.swift b/submodules/Display/Source/NavigationBarContentNode.swift similarity index 100% rename from submodules/Display/Display/NavigationBarContentNode.swift rename to submodules/Display/Source/NavigationBarContentNode.swift diff --git a/submodules/Display/Display/NavigationBarTitleTransitionNode.swift b/submodules/Display/Source/NavigationBarTitleTransitionNode.swift similarity index 100% rename from submodules/Display/Display/NavigationBarTitleTransitionNode.swift rename to submodules/Display/Source/NavigationBarTitleTransitionNode.swift diff --git a/submodules/Display/Display/NavigationBarTitleView.swift b/submodules/Display/Source/NavigationBarTitleView.swift similarity index 100% rename from submodules/Display/Display/NavigationBarTitleView.swift rename to submodules/Display/Source/NavigationBarTitleView.swift diff --git a/submodules/Display/Display/NavigationBarTransitionContainer.swift b/submodules/Display/Source/NavigationBarTransitionContainer.swift similarity index 100% rename from submodules/Display/Display/NavigationBarTransitionContainer.swift rename to submodules/Display/Source/NavigationBarTransitionContainer.swift diff --git a/submodules/Display/Display/NavigationBarTransitionState.swift b/submodules/Display/Source/NavigationBarTransitionState.swift similarity index 100% rename from submodules/Display/Display/NavigationBarTransitionState.swift rename to submodules/Display/Source/NavigationBarTransitionState.swift diff --git a/submodules/Display/Display/NavigationButtonNode.swift b/submodules/Display/Source/NavigationButtonNode.swift similarity index 96% rename from submodules/Display/Display/NavigationButtonNode.swift rename to submodules/Display/Source/NavigationButtonNode.swift index 43519c3cb9..62217eeac6 100644 --- a/submodules/Display/Display/NavigationButtonNode.swift +++ b/submodules/Display/Source/NavigationButtonNode.swift @@ -5,7 +5,7 @@ public protocol NavigationButtonCustomDisplayNode { var isHighlightable: Bool { get } } -private final class NavigationButtonItemNode: ASTextNode { +private final class NavigationButtonItemNode: ImmediateTextNode { private func fontForCurrentState() -> UIFont { return self.bold ? UIFont.boldSystemFont(ofSize: 17.0) : UIFont.systemFont(ofSize: 17.0) } @@ -202,8 +202,8 @@ private final class NavigationButtonItemNode: ASTextNode { self.accessibilityTraits = .button } - func updateLayout(_ constrainedSize: CGSize) -> CGSize { - let superSize = super.calculateSizeThatFits(constrainedSize) + override func updateLayout(_ constrainedSize: CGSize) -> CGSize { + let superSize = super.updateLayout(constrainedSize) if let node = self.node { let nodeSize = node.measure(constrainedSize) @@ -280,15 +280,13 @@ private final class NavigationButtonItemNode: ASTextNode { } } - public override var isEnabled: Bool { - get { - return super.isEnabled - } - set(value) { - if self.isEnabled != value { - super.isEnabled = value - - self.attributedText = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) + public var isEnabled: Bool = true { + didSet { + if self.isEnabled != oldValue { + self.attributedText = NSAttributedString(string: self.text, attributes: self.attributesForCurrentState()) + if let constrainedSize = self.constrainedSize { + let _ = self.updateLayout(constrainedSize) + } } } } diff --git a/submodules/Display/Display/NavigationTitleNode.swift b/submodules/Display/Source/NavigationTitleNode.swift similarity index 89% rename from submodules/Display/Display/NavigationTitleNode.swift rename to submodules/Display/Source/NavigationTitleNode.swift index 458bc461ff..ef3db73f23 100644 --- a/submodules/Display/Display/NavigationTitleNode.swift +++ b/submodules/Display/Source/NavigationTitleNode.swift @@ -2,7 +2,7 @@ import UIKit import AsyncDisplayKit public class NavigationTitleNode: ASDisplayNode { - private let label: ASTextNode + private let label: ImmediateTextNode private var _text: NSString = "" public var text: NSString { @@ -22,9 +22,9 @@ public class NavigationTitleNode: ASDisplayNode { } public init(text: NSString) { - self.label = ASTextNode() + self.label = ImmediateTextNode() self.label.maximumNumberOfLines = 1 - self.label.truncationMode = .byTruncatingTail + self.label.truncationType = .end self.label.displaysAsynchronously = false super.init() @@ -48,7 +48,7 @@ public class NavigationTitleNode: ASDisplayNode { } public override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { - self.label.measure(constrainedSize) + self.label.updateLayout(constrainedSize) return self.label.calculatedSize } diff --git a/submodules/Display/Display/NavigationTransitionCoordinator.swift b/submodules/Display/Source/NavigationTransitionCoordinator.swift similarity index 99% rename from submodules/Display/Display/NavigationTransitionCoordinator.swift rename to submodules/Display/Source/NavigationTransitionCoordinator.swift index 48aec470c2..692f8fa287 100644 --- a/submodules/Display/Display/NavigationTransitionCoordinator.swift +++ b/submodules/Display/Source/NavigationTransitionCoordinator.swift @@ -1,5 +1,6 @@ import UIKit import AppBundle +import AsyncDisplayKit enum NavigationTransition { case Push @@ -63,7 +64,6 @@ final class NavigationTransitionCoordinator { self.dimNode.backgroundColor = UIColor.black self.shadowNode = ASImageNode() self.shadowNode.displaysAsynchronously = false - self.shadowNode.displayWithoutProcessing = true self.shadowNode.image = shadowImage if let topNavigationBar = topNavigationBar, let bottomNavigationBar = bottomNavigationBar { diff --git a/submodules/Display/Source/Nodes/ASImageNode.swift b/submodules/Display/Source/Nodes/ASImageNode.swift new file mode 100644 index 0000000000..8baa7359e2 --- /dev/null +++ b/submodules/Display/Source/Nodes/ASImageNode.swift @@ -0,0 +1,34 @@ +import Foundation +import UIKit +import AsyncDisplayKit + +open class ASImageNode: ASDisplayNode { + public var image: UIImage? { + didSet { + if let image = self.image { + let capInsets = image.capInsets + if capInsets.left.isZero && capInsets.top.isZero { + self.contentsScale = image.scale + self.contents = image.cgImage + } else { + ASDisplayNodeSetResizableContents(self, image) + } + } else { + self.contents = nil + } + if self.image?.size != oldValue?.size { + self.invalidateCalculatedLayout() + } + } + } + + public var displayWithoutProcessing: Bool = true + + override public init() { + super.init() + } + + override public func calculateSizeThatFits(_ contrainedSize: CGSize) -> CGSize { + return self.image?.size ?? CGSize() + } +} diff --git a/submodules/Display/Source/Nodes/ButtonNode.swift b/submodules/Display/Source/Nodes/ButtonNode.swift new file mode 100644 index 0000000000..927eea4f7e --- /dev/null +++ b/submodules/Display/Source/Nodes/ButtonNode.swift @@ -0,0 +1,350 @@ +import Foundation +import UIKit +import AsyncDisplayKit + +open class ASButtonNode: ASControlNode { + public let titleNode: ImmediateTextNode + public let highlightedTitleNode: ImmediateTextNode + public let disabledTitleNode: ImmediateTextNode + public let imageNode: ASImageNode + public let disabledImageNode: ASImageNode + public let backgroundImageNode: ASImageNode + public let highlightedBackgroundImageNode: ASImageNode + + public var contentEdgeInsets: UIEdgeInsets = UIEdgeInsets() { + didSet { + if self.contentEdgeInsets != oldValue { + self.invalidateCalculatedLayout() + self.setNeedsLayout() + } + } + } + + public var contentHorizontalAlignment: ASHorizontalAlignment = .middle { + didSet { + if self.contentHorizontalAlignment != oldValue { + self.invalidateCalculatedLayout() + self.setNeedsLayout() + } + } + } + + public var laysOutHorizontally: Bool = true { + didSet { + if self.laysOutHorizontally != oldValue { + self.invalidateCalculatedLayout() + self.setNeedsLayout() + } + } + } + + public var contentSpacing: CGFloat = 0.0 { + didSet { + if self.contentSpacing != oldValue { + self.invalidateCalculatedLayout() + self.setNeedsLayout() + } + } + } + + private var calculatedTitleSize: CGSize = CGSize() + private var calculatedHighlightedTitleSize: CGSize = CGSize() + private var calculatedDisabledTitleSize: CGSize = CGSize() + + override public init() { + self.titleNode = ImmediateTextNode() + self.titleNode.isUserInteractionEnabled = false + self.titleNode.displaysAsynchronously = false + + self.highlightedTitleNode = ImmediateTextNode() + self.highlightedTitleNode.isUserInteractionEnabled = false + self.highlightedTitleNode.displaysAsynchronously = false + + self.disabledTitleNode = ImmediateTextNode() + self.disabledTitleNode.isUserInteractionEnabled = false + self.disabledTitleNode.displaysAsynchronously = false + + self.imageNode = ASImageNode() + self.imageNode.isUserInteractionEnabled = false + self.imageNode.displaysAsynchronously = false + self.imageNode.displayWithoutProcessing = true + + self.disabledImageNode = ASImageNode() + self.disabledImageNode.isUserInteractionEnabled = false + self.disabledImageNode.displaysAsynchronously = false + self.disabledImageNode.displayWithoutProcessing = true + + self.backgroundImageNode = ASImageNode() + self.backgroundImageNode.isUserInteractionEnabled = false + self.backgroundImageNode.displaysAsynchronously = false + self.backgroundImageNode.displayWithoutProcessing = true + + self.highlightedBackgroundImageNode = ASImageNode() + self.highlightedBackgroundImageNode.isUserInteractionEnabled = false + self.highlightedBackgroundImageNode.displaysAsynchronously = false + self.highlightedBackgroundImageNode.displayWithoutProcessing = true + + super.init() + + self.addSubnode(self.backgroundImageNode) + self.addSubnode(self.highlightedBackgroundImageNode) + self.highlightedBackgroundImageNode.isHidden = true + self.addSubnode(self.titleNode) + self.addSubnode(self.highlightedTitleNode) + self.highlightedTitleNode.isHidden = true + self.addSubnode(self.disabledTitleNode) + self.disabledTitleNode.isHidden = true + self.addSubnode(self.imageNode) + self.addSubnode(self.disabledImageNode) + self.disabledImageNode.isHidden = true + } + + override open func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + let horizontalInsets = self.contentEdgeInsets.left + self.contentEdgeInsets.right + let verticalInsets = self.contentEdgeInsets.top + self.contentEdgeInsets.bottom + + let imageSize = self.imageNode.image?.size ?? CGSize() + + let widthForTitle: CGFloat + if self.laysOutHorizontally { + widthForTitle = max(1.0, constrainedSize.width - horizontalInsets - imageSize.width - (imageSize.width.isZero ? 0.0 : self.contentSpacing)) + } else { + widthForTitle = max(1.0, constrainedSize.width - horizontalInsets) + } + + let normalTitleSize = self.titleNode.updateLayout(CGSize(width: widthForTitle, height: max(1.0, constrainedSize.height - verticalInsets))) + self.calculatedTitleSize = normalTitleSize + let highlightedTitleSize = self.highlightedTitleNode.updateLayout(CGSize(width: widthForTitle, height: max(1.0, constrainedSize.height - verticalInsets))) + self.calculatedHighlightedTitleSize = highlightedTitleSize + self.calculatedDisabledTitleSize = self.disabledTitleNode.updateLayout(CGSize(width: widthForTitle, height: max(1.0, constrainedSize.height - verticalInsets))) + + let titleSize = CGSize(width: max(normalTitleSize.width, highlightedTitleSize.width), height: max(normalTitleSize.height, highlightedTitleSize.height)) + + var contentSize: CGSize + if self.laysOutHorizontally { + contentSize = CGSize(width: titleSize.width + imageSize.width, height: max(titleSize.height, imageSize.height)) + if !titleSize.width.isZero && !imageSize.width.isZero { + contentSize.width += self.contentSpacing + } + } else { + contentSize = CGSize(width: max(titleSize.width, imageSize.width), height: titleSize.height + imageSize.height) + if !titleSize.width.isZero && !imageSize.width.isZero { + contentSize.height += self.contentSpacing + } + } + + return CGSize(width: min(constrainedSize.width, contentSize.width + self.contentEdgeInsets.left + self.contentEdgeInsets.right), height: min(constrainedSize.height, contentSize.height + self.contentEdgeInsets.top + self.contentEdgeInsets.bottom)) + } + + open func setAttributedTitle(_ title: NSAttributedString, for state: UIControl.State) { + if state == [] { + if let attributedText = self.titleNode.attributedText { + if !attributedText.isEqual(to: title) { + self.invalidateCalculatedLayout() + self.setNeedsLayout() + } + } else { + self.invalidateCalculatedLayout() + self.setNeedsLayout() + } + self.titleNode.attributedText = title + + if let attributedText = self.highlightedTitleNode.attributedText { + if !attributedText.isEqual(to: title) { + self.invalidateCalculatedLayout() + self.setNeedsLayout() + } + } else { + self.invalidateCalculatedLayout() + self.setNeedsLayout() + } + self.highlightedTitleNode.attributedText = title + } else if state == .highlighted || state == .selected { + if let attributedText = self.highlightedTitleNode.attributedText { + if !attributedText.isEqual(to: title) { + self.invalidateCalculatedLayout() + self.setNeedsLayout() + } + } else { + self.invalidateCalculatedLayout() + self.setNeedsLayout() + } + self.highlightedTitleNode.attributedText = title + } else if state == .disabled { + if let attributedText = self.disabledTitleNode.attributedText { + if !attributedText.isEqual(to: title) { + self.invalidateCalculatedLayout() + self.setNeedsLayout() + } + } else { + self.invalidateCalculatedLayout() + self.setNeedsLayout() + } + self.disabledTitleNode.attributedText = title + } else { + if let attributedText = self.titleNode.attributedText { + if !attributedText.isEqual(to: title) { + self.invalidateCalculatedLayout() + self.setNeedsLayout() + } + } else { + self.invalidateCalculatedLayout() + self.setNeedsLayout() + } + self.titleNode.attributedText = title + } + } + + open func attributedTitle(for state: UIControl.State) -> NSAttributedString? { + if state == .highlighted || state == .selected { + return self.highlightedTitleNode.attributedText + } else if state == .disabled { + return self.disabledTitleNode.attributedText + } else { + return self.titleNode.attributedText + } + } + + open func setTitle(_ title: String, with font: UIFont, with color: UIColor, for state: UIControl.State) { + self.setAttributedTitle(NSAttributedString(string: title, font: font, textColor: color), for: state) + } + + open func setImage(_ image: UIImage?, for state: UIControl.State) { + if image?.size != self.imageNode.image?.size { + self.invalidateCalculatedLayout() + self.setNeedsLayout() + } + if state == .disabled { + self.disabledImageNode.image = image + } else { + self.imageNode.image = image + } + } + + open func setBackgroundImage(_ image: UIImage?, for state: UIControl.State) { + if state == [] { + self.backgroundImageNode.image = image + self.highlightedBackgroundImageNode.image = image + } else if state == .highlighted || state == .selected { + self.highlightedBackgroundImageNode.image = image + } else { + self.backgroundImageNode.image = image + } + } + + open func image(for state: UIControl.State) -> UIImage? { + switch state { + case .disabled: + return self.disabledImageNode.image ?? self.imageNode.image + default: + return self.imageNode.image + } + } + + open func backgroundImage(for state: UIControl.State) -> UIImage? { + return self.backgroundImageNode.image + } + + override open var isHighlighted: Bool { + didSet { + if self.isHighlighted != oldValue { + if self.isHighlighted { + if self.highlightedTitleNode.attributedText != nil { + self.highlightedTitleNode.isHidden = false + self.titleNode.isHidden = true + } else { + self.highlightedTitleNode.isHidden = true + self.titleNode.isHidden = false + } + if self.highlightedBackgroundImageNode.image != nil { + self.highlightedBackgroundImageNode.isHidden = false + self.backgroundImageNode.isHidden = true + } else { + self.highlightedBackgroundImageNode.isHidden = true + self.backgroundImageNode.isHidden = false + } + } else { + self.highlightedTitleNode.isHidden = true + self.titleNode.isHidden = false + + self.highlightedBackgroundImageNode.isHidden = true + self.backgroundImageNode.isHidden = false + } + } + } + } + + override open var isEnabled: Bool { + didSet { + if self.isEnabled != oldValue { + if self.isEnabled || self.disabledTitleNode.attributedText == nil { + self.titleNode.isHidden = false + self.disabledTitleNode.isHidden = true + } else { + self.titleNode.isHidden = true + self.disabledTitleNode.isHidden = false + } + + if self.isEnabled || self.disabledImageNode.image == nil { + self.imageNode.isHidden = false + self.disabledImageNode.isHidden = true + } else { + self.imageNode.isHidden = true + self.disabledImageNode.isHidden = false + } + } + } + } + + override open func layout() { + let size = self.bounds.size + + let contentRect = CGRect(origin: CGPoint(x: self.contentEdgeInsets.left, y: self.contentEdgeInsets.top), size: CGSize(width: size.width - self.contentEdgeInsets.left - self.contentEdgeInsets.right, height: size.height - self.contentEdgeInsets.top - self.contentEdgeInsets.bottom)) + + let imageSize = self.imageNode.image?.size ?? CGSize() + + let titleOrigin: CGPoint + let highlightedTitleOrigin: CGPoint + let disabledTitleOrigin: CGPoint + let imageOrigin: CGPoint + + if self.laysOutHorizontally { + switch self.contentHorizontalAlignment { + case .left: + titleOrigin = CGPoint(x: contentRect.minX, y: contentRect.minY + floor((contentRect.height - self.calculatedTitleSize.height) / 2.0)) + highlightedTitleOrigin = CGPoint(x: contentRect.minX, y: contentRect.minY + floor((contentRect.height - self.calculatedHighlightedTitleSize.height) / 2.0)) + disabledTitleOrigin = CGPoint(x: contentRect.minX, y: contentRect.minY + floor((contentRect.height - self.calculatedDisabledTitleSize.height) / 2.0)) + imageOrigin = CGPoint(x: titleOrigin.x + self.calculatedTitleSize.width + self.contentSpacing, y: contentRect.minY + floor((contentRect.height - imageSize.height) / 2.0)) + case .right: + titleOrigin = CGPoint(x: contentRect.maxX - self.calculatedTitleSize.width, y: contentRect.minY + floor((contentRect.height - self.calculatedTitleSize.height) / 2.0)) + highlightedTitleOrigin = CGPoint(x: contentRect.maxX - self.calculatedHighlightedTitleSize.width, y: contentRect.minY + floor((contentRect.height - self.calculatedHighlightedTitleSize.height) / 2.0)) + disabledTitleOrigin = CGPoint(x: contentRect.maxX - self.calculatedDisabledTitleSize.width, y: contentRect.minY + floor((contentRect.height - self.calculatedDisabledTitleSize.height) / 2.0)) + imageOrigin = CGPoint(x: titleOrigin.x - self.contentSpacing - imageSize.width, y: contentRect.minY + floor((contentRect.height - imageSize.height) / 2.0)) + default: + titleOrigin = CGPoint(x: contentRect.minX + floor((contentRect.width - self.calculatedTitleSize.width) / 2.0), y: contentRect.minY + floor((contentRect.height - self.calculatedTitleSize.height) / 2.0)) + highlightedTitleOrigin = CGPoint(x: floor((contentRect.width - self.calculatedHighlightedTitleSize.width) / 2.0), y: contentRect.minY + floor((contentRect.height - self.calculatedHighlightedTitleSize.height) / 2.0)) + disabledTitleOrigin = CGPoint(x: floor((contentRect.width - self.calculatedDisabledTitleSize.width) / 2.0), y: contentRect.minY + floor((contentRect.height - self.calculatedDisabledTitleSize.height) / 2.0)) + imageOrigin = CGPoint(x: floor((contentRect.width - imageSize.width) / 2.0), y: contentRect.minY + floor((contentRect.height - imageSize.height) / 2.0)) + } + } else { + var contentHeight: CGFloat = self.calculatedTitleSize.height + if !imageSize.height.isZero { + contentHeight += self.contentSpacing + imageSize.height + } + let contentY = contentRect.minY + floor((contentRect.height - contentHeight) / 2.0) + titleOrigin = CGPoint(x: contentRect.minX + floor((contentRect.width - self.calculatedTitleSize.width) / 2.0), y: contentY + contentHeight - self.calculatedTitleSize.height) + highlightedTitleOrigin = CGPoint(x: contentRect.minX + floor((contentRect.width - self.calculatedHighlightedTitleSize.width) / 2.0), y: contentY + contentHeight - self.calculatedHighlightedTitleSize.height) + disabledTitleOrigin = CGPoint(x: contentRect.minX + floor((contentRect.width - self.calculatedDisabledTitleSize.width) / 2.0), y: contentY + contentHeight - self.calculatedDisabledTitleSize.height) + imageOrigin = CGPoint(x: floor((contentRect.width - imageSize.width) / 2.0), y: contentY) + } + + self.titleNode.frame = CGRect(origin: titleOrigin, size: self.calculatedTitleSize) + self.highlightedTitleNode.frame = CGRect(origin: highlightedTitleOrigin, size: self.calculatedHighlightedTitleSize) + self.disabledTitleNode.frame = CGRect(origin: disabledTitleOrigin, size: self.calculatedDisabledTitleSize) + self.imageNode.frame = CGRect(origin: imageOrigin, size: imageSize) + self.disabledImageNode.frame = CGRect(origin: imageOrigin, size: imageSize) + + self.backgroundImageNode.frame = CGRect(origin: CGPoint(), size: size) + self.highlightedBackgroundImageNode.frame = CGRect(origin: CGPoint(), size: size) + } +} diff --git a/submodules/Display/Display/PageControlNode.swift b/submodules/Display/Source/PageControlNode.swift similarity index 98% rename from submodules/Display/Display/PageControlNode.swift rename to submodules/Display/Source/PageControlNode.swift index 283d7f6bd3..0b600ac17c 100644 --- a/submodules/Display/Display/PageControlNode.swift +++ b/submodules/Display/Source/PageControlNode.swift @@ -58,7 +58,6 @@ public final class PageControlNode: ASDisplayNode { let dotNode = ASImageNode() dotNode.image = self.normalDotImage dotNode.displaysAsynchronously = false - dotNode.displayWithoutProcessing = true dotNode.isUserInteractionEnabled = false self.dotNodes.append(dotNode) self.addSubnode(dotNode) diff --git a/submodules/Display/Display/PeekController.swift b/submodules/Display/Source/PeekController.swift similarity index 100% rename from submodules/Display/Display/PeekController.swift rename to submodules/Display/Source/PeekController.swift diff --git a/submodules/Display/Display/PeekControllerContent.swift b/submodules/Display/Source/PeekControllerContent.swift similarity index 100% rename from submodules/Display/Display/PeekControllerContent.swift rename to submodules/Display/Source/PeekControllerContent.swift diff --git a/submodules/Display/Display/PeekControllerGestureRecognizer.swift b/submodules/Display/Source/PeekControllerGestureRecognizer.swift similarity index 100% rename from submodules/Display/Display/PeekControllerGestureRecognizer.swift rename to submodules/Display/Source/PeekControllerGestureRecognizer.swift diff --git a/submodules/Display/Display/PeekControllerMenuItemNode.swift b/submodules/Display/Source/PeekControllerMenuItemNode.swift similarity index 95% rename from submodules/Display/Display/PeekControllerMenuItemNode.swift rename to submodules/Display/Source/PeekControllerMenuItemNode.swift index 5d4890b1da..b91fc4aed6 100644 --- a/submodules/Display/Display/PeekControllerMenuItemNode.swift +++ b/submodules/Display/Source/PeekControllerMenuItemNode.swift @@ -32,7 +32,7 @@ final class PeekControllerMenuItemNode: HighlightTrackingButtonNode { private let separatorNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode - private let textNode: ASTextNode + private let textNode: ImmediateTextNode init(theme: PeekControllerTheme, item: PeekControllerMenuItem, activatedAction: @escaping () -> Void) { self.item = item @@ -47,7 +47,7 @@ final class PeekControllerMenuItemNode: HighlightTrackingButtonNode { self.highlightedBackgroundNode.backgroundColor = theme.menuItemHighligtedColor self.highlightedBackgroundNode.alpha = 0.0 - self.textNode = ASTextNode() + self.textNode = ImmediateTextNode() self.textNode.isUserInteractionEnabled = false self.textNode.displaysAsynchronously = false @@ -93,7 +93,7 @@ final class PeekControllerMenuItemNode: HighlightTrackingButtonNode { transition.updateFrame(node: self.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: height))) transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: height), size: CGSize(width: width, height: UIScreenPixel))) - let textSize = self.textNode.measure(CGSize(width: width - 10.0, height: height)) + let textSize = self.textNode.updateLayout(CGSize(width: width - 10.0, height: height)) transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((width - textSize.width) / 2.0), y: floor((height - textSize.height) / 2.0)), size: textSize)) return height diff --git a/submodules/Display/Display/PeekControllerMenuNode.swift b/submodules/Display/Source/PeekControllerMenuNode.swift similarity index 100% rename from submodules/Display/Display/PeekControllerMenuNode.swift rename to submodules/Display/Source/PeekControllerMenuNode.swift diff --git a/submodules/Display/Display/PeekControllerNode.swift b/submodules/Display/Source/PeekControllerNode.swift similarity index 99% rename from submodules/Display/Display/PeekControllerNode.swift rename to submodules/Display/Source/PeekControllerNode.swift index 9b80c23a40..fabe9c53b9 100644 --- a/submodules/Display/Display/PeekControllerNode.swift +++ b/submodules/Display/Source/PeekControllerNode.swift @@ -46,7 +46,6 @@ final class PeekControllerNode: ViewControllerTracingNode { self.containerBackgroundNode = ASImageNode() self.containerBackgroundNode.isLayerBacked = true - self.containerBackgroundNode.displayWithoutProcessing = true self.containerBackgroundNode.displaysAsynchronously = false self.containerNode = ASDisplayNode() diff --git a/submodules/Display/Display/PresentationContext.swift b/submodules/Display/Source/PresentationContext.swift similarity index 99% rename from submodules/Display/Display/PresentationContext.swift rename to submodules/Display/Source/PresentationContext.swift index 651db2cd8e..8c47306966 100644 --- a/submodules/Display/Display/PresentationContext.swift +++ b/submodules/Display/Source/PresentationContext.swift @@ -198,7 +198,6 @@ public final class PresentationContext { } } (controller as? UIViewController)?.setIgnoreAppearanceMethodInvocations(false) - view.layer.invalidateUpTheTree() strongSelf.updateViews() controller.viewWillAppear(false) if let controller = controller as? PresentableController { diff --git a/submodules/Display/Display/RuntimeUtils.swift b/submodules/Display/Source/RuntimeUtils.swift similarity index 100% rename from submodules/Display/Display/RuntimeUtils.swift rename to submodules/Display/Source/RuntimeUtils.swift diff --git a/submodules/Display/Display/ScrollToTopProxyView.swift b/submodules/Display/Source/ScrollToTopProxyView.swift similarity index 98% rename from submodules/Display/Display/ScrollToTopProxyView.swift rename to submodules/Display/Source/ScrollToTopProxyView.swift index d0e8eb459e..b7aa0d8d9c 100644 --- a/submodules/Display/Display/ScrollToTopProxyView.swift +++ b/submodules/Display/Source/ScrollToTopProxyView.swift @@ -1,4 +1,5 @@ import UIKit +import AsyncDisplayKit class ScrollToTopView: UIScrollView, UIScrollViewDelegate { var action: (() -> Void)? diff --git a/submodules/Display/Display/ShakeAnimation.swift b/submodules/Display/Source/ShakeAnimation.swift similarity index 100% rename from submodules/Display/Display/ShakeAnimation.swift rename to submodules/Display/Source/ShakeAnimation.swift diff --git a/submodules/Display/Display/Spring.swift b/submodules/Display/Source/Spring.swift similarity index 100% rename from submodules/Display/Display/Spring.swift rename to submodules/Display/Source/Spring.swift diff --git a/submodules/Display/Display/StatusBar.swift b/submodules/Display/Source/StatusBar.swift similarity index 95% rename from submodules/Display/Display/StatusBar.swift rename to submodules/Display/Source/StatusBar.swift index c316f014a7..c7cc1fe8ba 100644 --- a/submodules/Display/Display/StatusBar.swift +++ b/submodules/Display/Source/StatusBar.swift @@ -38,7 +38,7 @@ private func addInCallAnimation(_ layer: CALayer) { layer.add(animation, forKey: "blink") } -private final class StatusBarLabelNode: ASTextNode { +private final class StatusBarLabelNode: ImmediateTextNode { override func willEnterHierarchy() { super.willEnterHierarchy() @@ -101,7 +101,6 @@ public final class StatusBar: ASDisplayNode { didSet { if !self.verticalOffset.isEqual(to: oldValue) { self.offsetNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -self.verticalOffset), size: CGSize()) - self.layer.invalidateUpTheTree() } } } @@ -119,7 +118,7 @@ public final class StatusBar: ASDisplayNode { self.offsetNode.isUserInteractionEnabled = false - let labelSize = self.inCallLabel.measure(CGSize(width: 300.0, height: 300.0)) + let labelSize = self.inCallLabel.updateLayout(CGSize(width: 300.0, height: 300.0)) self.inCallLabel.frame = CGRect(origin: CGPoint(x: 10.0, y: 20.0 + 4.0), size: labelSize) super.init() @@ -133,8 +132,6 @@ public final class StatusBar: ASDisplayNode { self.addSubnode(self.offsetNode) self.addSubnode(self.inCallBackgroundNode) - self.layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: true, userData: self, tracingTag: WindowTracingTags.statusBar, disableChildrenTracingTags: 0)) - self.clipsToBounds = true self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) @@ -259,7 +256,7 @@ public final class StatusBar: ASDisplayNode { if self.inCallLabel.supernode != nil { let size = self.bounds.size if !size.width.isZero && !size.height.isZero { - let labelSize = self.inCallLabel.measure(size) + let labelSize = self.inCallLabel.updateLayout(size) self.inCallLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - labelSize.width) / 2.0), y: 20.0 + floor((20.0 - labelSize.height) / 2.0)), size: labelSize) } } diff --git a/submodules/Display/Display/StatusBarHost.swift b/submodules/Display/Source/StatusBarHost.swift similarity index 100% rename from submodules/Display/Display/StatusBarHost.swift rename to submodules/Display/Source/StatusBarHost.swift diff --git a/submodules/Display/Display/StatusBarProxyNode.swift b/submodules/Display/Source/StatusBarProxyNode.swift similarity index 99% rename from submodules/Display/Display/StatusBarProxyNode.swift rename to submodules/Display/Source/StatusBarProxyNode.swift index 24ae8f815b..c429a8c176 100644 --- a/submodules/Display/Display/StatusBarProxyNode.swift +++ b/submodules/Display/Source/StatusBarProxyNode.swift @@ -1,6 +1,7 @@ import Foundation import UIKit import AsyncDisplayKit +import ObjCRuntimeUtils public enum StatusBarStyle { case Black diff --git a/submodules/Display/Display/SubstringSearch.swift b/submodules/Display/Source/SubstringSearch.swift similarity index 100% rename from submodules/Display/Display/SubstringSearch.swift rename to submodules/Display/Source/SubstringSearch.swift diff --git a/submodules/Display/Display/SwitchNode.swift b/submodules/Display/Source/SwitchNode.swift similarity index 100% rename from submodules/Display/Display/SwitchNode.swift rename to submodules/Display/Source/SwitchNode.swift diff --git a/submodules/Display/Display/TabBarContollerNode.swift b/submodules/Display/Source/TabBarContollerNode.swift similarity index 95% rename from submodules/Display/Display/TabBarContollerNode.swift rename to submodules/Display/Source/TabBarContollerNode.swift index dee3c1f46a..c84927e88a 100644 --- a/submodules/Display/Display/TabBarContollerNode.swift +++ b/submodules/Display/Source/TabBarContollerNode.swift @@ -25,10 +25,10 @@ final class TabBarControllerNode: ASDisplayNode { } } - init(theme: TabBarControllerTheme, navigationBar: NavigationBar?, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, contextAction: @escaping (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void, toolbarActionSelected: @escaping (ToolbarActionOption) -> Void) { + init(theme: TabBarControllerTheme, navigationBar: NavigationBar?, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, contextAction: @escaping (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void, swipeAction: @escaping (Int, TabBarItemSwipeDirection) -> Void, toolbarActionSelected: @escaping (ToolbarActionOption) -> Void) { self.theme = theme self.navigationBar = navigationBar - self.tabBarNode = TabBarNode(theme: theme, itemSelected: itemSelected, contextAction: contextAction) + self.tabBarNode = TabBarNode(theme: theme, itemSelected: itemSelected, contextAction: contextAction, swipeAction: swipeAction) self.toolbarActionSelected = toolbarActionSelected super.init() diff --git a/submodules/Display/Display/TabBarController.swift b/submodules/Display/Source/TabBarController.swift similarity index 94% rename from submodules/Display/Display/TabBarController.swift rename to submodules/Display/Source/TabBarController.swift index bf48fe944b..36b93c9b5f 100644 --- a/submodules/Display/Display/TabBarController.swift +++ b/submodules/Display/Source/TabBarController.swift @@ -166,6 +166,21 @@ open class TabBarController: ViewController { return self.tabBarControllerNode.tabBarNode.sourceNodesForController(at: index) } + public func frameForControllerTab(controller: ViewController) -> CGRect? { + if let index = self.controllers.firstIndex(of: controller) { + return self.tabBarControllerNode.tabBarNode.frameForControllerTab(at: index).flatMap { self.tabBarControllerNode.tabBarNode.view.convert($0, to: self.view) } + } else { + return nil + } + } + + public func isPointInsideContentArea(point: CGPoint) -> Bool { + if point.y < self.tabBarControllerNode.tabBarNode.frame.minY { + return true + } + return false + } + override open func loadDisplayNode() { self.displayNode = TabBarControllerNode(theme: self.theme, navigationBar: self.navigationBar, itemSelected: { [weak self] index, longTap, itemNodes in if let strongSelf = self { @@ -240,6 +255,13 @@ open class TabBarController: ViewController { if index >= 0 && index < strongSelf.controllers.count { strongSelf.controllers[index].tabBarItemContextAction(sourceNode: node, gesture: gesture) } + }, swipeAction: { [weak self] index, direction in + guard let strongSelf = self else { + return + } + if index >= 0 && index < strongSelf.controllers.count { + strongSelf.controllers[index].tabBarItemSwipeAction(direction: direction) + } }, toolbarActionSelected: { [weak self] action in self?.currentController?.toolbarActionSelected(action: action) }) @@ -378,7 +400,7 @@ open class TabBarController: ViewController { } } self.controllers = controllers - self.tabBarControllerNode.tabBarNode.tabBarItems = self.controllers.map({ TabBarNodeItem(item: $0.tabBarItem, hasContext: $0.hasTabBarItemContextAction) }) + self.tabBarControllerNode.tabBarNode.tabBarItems = self.controllers.map({ TabBarNodeItem(item: $0.tabBarItem, contextActionType: $0.tabBarItemContextActionType) }) let signals = combineLatest(self.controllers.map({ $0.tabBarItem }).map { tabBarItem -> Signal in if let tabBarItem = tabBarItem, tabBarItem.image == nil { diff --git a/submodules/Display/Display/TabBarNode.swift b/submodules/Display/Source/TabBarNode.swift similarity index 92% rename from submodules/Display/Display/TabBarNode.swift rename to submodules/Display/Source/TabBarNode.swift index c4e5b28033..4ae3ea97d8 100644 --- a/submodules/Display/Display/TabBarNode.swift +++ b/submodules/Display/Source/TabBarNode.swift @@ -2,6 +2,7 @@ import Foundation import UIKit import AsyncDisplayKit import SwiftSignalKit +import UIKitRuntimeUtils private let separatorHeight: CGFloat = 1.0 / UIScreen.main.scale private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: UIColor, tintColor: UIColor, horizontal: Bool, imageMode: Bool, centered: Bool = false) -> (UIImage, CGFloat) { @@ -88,6 +89,11 @@ private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: private let badgeFont = Font.regular(13.0) +public enum TabBarItemSwipeDirection { + case left + case right +} + private final class TabBarItemNode: ASDisplayNode { let extractedContainerNode: ContextExtractedContentContainingNode let containerNode: ContextControllerSourceNode @@ -96,6 +102,9 @@ private final class TabBarItemNode: ASDisplayNode { let contextImageNode: ASImageNode let contextTextImageNode: ASImageNode var contentWidth: CGFloat? + var isSelected: Bool = false + + var swiped: ((TabBarItemSwipeDirection) -> Void)? override init() { self.extractedContainerNode = ContextExtractedContentContainingNode() @@ -146,6 +155,26 @@ private final class TabBarItemNode: ASDisplayNode { transition.updateAlpha(node: strongSelf.contextImageNode, alpha: isExtracted ? 1.0 : 0.0) transition.updateAlpha(node: strongSelf.contextTextImageNode, alpha: isExtracted ? 1.0 : 0.0) } + + /*let leftSwipe = UISwipeGestureRecognizer(target: self, action: #selector(self.swipeGesture(_:))) + leftSwipe.direction = .left + self.containerNode.view.addGestureRecognizer(leftSwipe) + let rightSwipe = UISwipeGestureRecognizer(target: self, action: #selector(self.swipeGesture(_:))) + rightSwipe.direction = .right + self.containerNode.view.addGestureRecognizer(rightSwipe)*/ + } + + @objc private func swipeGesture(_ gesture: UISwipeGestureRecognizer) { + if case .ended = gesture.state { + self.containerNode.cancelGesture() + + switch gesture.direction { + case .left: + self.swiped?(.left) + default: + self.swiped?(.right) + } + } } } @@ -173,7 +202,7 @@ private final class TabBarNodeContainer { var selectedImageValue: UIImage? var appliedSelectedImageValue: UIImage? - init(item: TabBarNodeItem, imageNode: TabBarItemNode, updateBadge: @escaping (String) -> Void, updateTitle: @escaping (String, Bool) -> Void, updateImage: @escaping (UIImage?) -> Void, updateSelectedImage: @escaping (UIImage?) -> Void, contextAction: @escaping (ContextExtractedContentContainingNode, ContextGesture) -> Void) { + init(item: TabBarNodeItem, imageNode: TabBarItemNode, updateBadge: @escaping (String) -> Void, updateTitle: @escaping (String, Bool) -> Void, updateImage: @escaping (UIImage?) -> Void, updateSelectedImage: @escaping (UIImage?) -> Void, contextAction: @escaping (ContextExtractedContentContainingNode, ContextGesture) -> Void, swipeAction: @escaping (TabBarItemSwipeDirection) -> Void) { self.item = item.item self.imageNode = imageNode @@ -225,7 +254,24 @@ private final class TabBarNodeContainer { } contextAction(strongSelf.imageNode.extractedContainerNode, gesture) } - imageNode.containerNode.isGestureEnabled = item.hasContext + imageNode.swiped = { [weak imageNode] direction in + guard let imageNode = imageNode, imageNode.isSelected else { + return + } + swipeAction(direction) + } + imageNode.containerNode.isGestureEnabled = item.contextActionType != .none + let contextActionType = item.contextActionType + imageNode.containerNode.shouldBegin = { [weak imageNode] _ in + switch contextActionType { + case .none: + return false + case .always: + return true + case .whenActive: + return imageNode?.isSelected ?? false + } + } } deinit { @@ -238,11 +284,11 @@ private final class TabBarNodeContainer { final class TabBarNodeItem { let item: UITabBarItem - let hasContext: Bool + let contextActionType: TabBarItemContextActionType - init(item: UITabBarItem, hasContext: Bool) { + init(item: UITabBarItem, contextActionType: TabBarItemContextActionType) { self.item = item - self.hasContext = hasContext + self.contextActionType = contextActionType } } @@ -269,6 +315,7 @@ class TabBarNode: ASDisplayNode { private let itemSelected: (Int, Bool, [ASDisplayNode]) -> Void private let contextAction: (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void + private let swipeAction: (Int, TabBarItemSwipeDirection) -> Void private var theme: TabBarControllerTheme private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat)? @@ -282,9 +329,10 @@ class TabBarNode: ASDisplayNode { private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer? - init(theme: TabBarControllerTheme, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, contextAction: @escaping (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void) { + init(theme: TabBarControllerTheme, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, contextAction: @escaping (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void, swipeAction: @escaping (Int, TabBarItemSwipeDirection) -> Void) { self.itemSelected = itemSelected self.contextAction = contextAction + self.swipeAction = swipeAction self.theme = theme self.separatorNode = ASDisplayNode() @@ -359,6 +407,11 @@ class TabBarNode: ASDisplayNode { return [container.imageNode.imageNode, container.imageNode.textImageNode, container.badgeContainerNode] } + func frameForControllerTab(at index: Int) -> CGRect? { + let container = self.tabBarNodeContainers[index] + return container.imageNode.frame + } + private func reloadTabBarItems() { for node in self.tabBarNodeContainers { node.imageNode.removeFromSupernode() @@ -381,6 +434,8 @@ class TabBarNode: ASDisplayNode { }, contextAction: { [weak self] node, gesture in self?.tapRecognizer?.cancel() self?.contextAction(i, node, gesture) + }, swipeAction: { [weak self] direction in + self?.swipeAction(i, direction) }) if let selectedIndex = self.selectedIndex, selectedIndex == i { let (textImage, contentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered) @@ -389,6 +444,7 @@ class TabBarNode: ASDisplayNode { node.imageNode.image = image node.accessibilityLabel = item.item.title node.contentWidth = max(contentWidth, imageContentWidth) + node.isSelected = true } else { let (textImage, contentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered) let (image, imageContentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered) @@ -396,6 +452,7 @@ class TabBarNode: ASDisplayNode { node.accessibilityLabel = item.item.title node.imageNode.image = image node.contentWidth = max(contentWidth, imageContentWidth) + node.isSelected = false } container.badgeBackgroundNode.image = self.badgeImage node.extractedContainerNode.contentNode.addSubnode(container.badgeContainerNode) @@ -428,6 +485,7 @@ class TabBarNode: ASDisplayNode { node.contextTextImageNode.image = contextTextImage node.contextImageNode.image = contextImage node.contentWidth = max(contentWidth, imageContentWidth) + node.isSelected = true } else { let (textImage, contentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered) let (image, imageContentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered) @@ -439,6 +497,7 @@ class TabBarNode: ASDisplayNode { node.contextTextImageNode.image = contextTextImage node.contextImageNode.image = contextImage node.contentWidth = max(contentWidth, imageContentWidth) + node.isSelected = false } let updatedImageSize = node.imageNode.image?.size ?? CGSize() diff --git a/submodules/Display/Display/TabBarTapRecognizer.swift b/submodules/Display/Source/TabBarTapRecognizer.swift similarity index 100% rename from submodules/Display/Display/TabBarTapRecognizer.swift rename to submodules/Display/Source/TabBarTapRecognizer.swift diff --git a/submodules/Display/Display/TapLongTapOrDoubleTapGestureRecognizer.swift b/submodules/Display/Source/TapLongTapOrDoubleTapGestureRecognizer.swift similarity index 100% rename from submodules/Display/Display/TapLongTapOrDoubleTapGestureRecognizer.swift rename to submodules/Display/Source/TapLongTapOrDoubleTapGestureRecognizer.swift diff --git a/submodules/Display/Display/TextAlertController.swift b/submodules/Display/Source/TextAlertController.swift similarity index 97% rename from submodules/Display/Display/TextAlertController.swift rename to submodules/Display/Source/TextAlertController.swift index d1d4d3f99e..d94ee19393 100644 --- a/submodules/Display/Display/TextAlertController.swift +++ b/submodules/Display/Source/TextAlertController.swift @@ -116,7 +116,7 @@ public final class TextAlertContentNode: AlertContentNode { private var theme: AlertControllerTheme private let actionLayout: TextAlertContentActionLayout - private let titleNode: ASTextNode? + private let titleNode: ImmediateTextNode? private let textNode: ImmediateTextNode private let actionNodesSeparator: ASDisplayNode @@ -152,12 +152,12 @@ public final class TextAlertContentNode: AlertContentNode { self.theme = theme self.actionLayout = actionLayout if let title = title { - let titleNode = ASTextNode() + let titleNode = ImmediateTextNode() titleNode.attributedText = title titleNode.displaysAsynchronously = false titleNode.isUserInteractionEnabled = false titleNode.maximumNumberOfLines = 2 - titleNode.truncationMode = .byTruncatingTail + titleNode.truncationType = .end titleNode.isAccessibilityElement = true self.titleNode = titleNode } else { @@ -252,7 +252,7 @@ public final class TextAlertContentNode: AlertContentNode { var titleSize: CGSize? if let titleNode = self.titleNode { - titleSize = titleNode.measure(CGSize(width: size.width - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude)) + titleSize = titleNode.updateLayout(CGSize(width: size.width - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude)) } let textSize = self.textNode.updateLayout(CGSize(width: size.width - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude)) @@ -264,7 +264,7 @@ public final class TextAlertContentNode: AlertContentNode { var effectiveActionLayout = self.actionLayout for actionNode in self.actionNodes { - let actionTitleSize = actionNode.titleNode.measure(CGSize(width: maxActionWidth, height: actionButtonHeight)) + let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { effectiveActionLayout = .vertical } diff --git a/submodules/Display/Display/TextFieldNode.swift b/submodules/Display/Source/TextFieldNode.swift similarity index 100% rename from submodules/Display/Display/TextFieldNode.swift rename to submodules/Display/Source/TextFieldNode.swift diff --git a/submodules/Display/Display/TextNode.swift b/submodules/Display/Source/TextNode.swift similarity index 95% rename from submodules/Display/Display/TextNode.swift rename to submodules/Display/Source/TextNode.swift index 2cbb4708ca..cbde782489 100644 --- a/submodules/Display/Display/TextNode.swift +++ b/submodules/Display/Source/TextNode.swift @@ -132,7 +132,8 @@ public final class TextNodeLayout: NSObject { fileprivate let truncationType: CTLineTruncationType fileprivate let backgroundColor: UIColor? fileprivate let constrainedSize: CGSize - fileprivate let alignment: NSTextAlignment + fileprivate let explicitAlignment: NSTextAlignment + fileprivate let resolvedAlignment: NSTextAlignment fileprivate let lineSpacing: CGFloat fileprivate let cutout: TextNodeCutout? fileprivate let insets: UIEdgeInsets @@ -147,12 +148,13 @@ public final class TextNodeLayout: NSObject { fileprivate let textStroke: (UIColor, CGFloat)? public let hasRTL: Bool - fileprivate init(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacing: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, size: CGSize, rawTextSize: CGSize, truncated: Bool, firstLineOffset: CGFloat, lines: [TextNodeLine], blockQuotes: [TextNodeBlockQuote], backgroundColor: UIColor?, lineColor: UIColor?, textShadowColor: UIColor?, textStroke: (UIColor, CGFloat)?) { + fileprivate init(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, explicitAlignment: NSTextAlignment, resolvedAlignment: NSTextAlignment, lineSpacing: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, size: CGSize, rawTextSize: CGSize, truncated: Bool, firstLineOffset: CGFloat, lines: [TextNodeLine], blockQuotes: [TextNodeBlockQuote], backgroundColor: UIColor?, lineColor: UIColor?, textShadowColor: UIColor?, textStroke: (UIColor, CGFloat)?) { self.attributedString = attributedString self.maximumNumberOfLines = maximumNumberOfLines self.truncationType = truncationType self.constrainedSize = constrainedSize - self.alignment = alignment + self.explicitAlignment = explicitAlignment + self.resolvedAlignment = resolvedAlignment self.lineSpacing = lineSpacing self.cutout = cutout self.insets = insets @@ -240,7 +242,7 @@ public final class TextNodeLayout: NSObject { for line in self.lines { lineIndex += 1 var lineFrame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: line.frame.origin.y - line.frame.size.height + self.firstLineOffset), size: line.frame.size) - switch self.alignment { + switch self.resolvedAlignment { case .center: lineFrame.origin.x = floor((self.size.width - lineFrame.size.width) / 2.0) case .natural: @@ -308,7 +310,7 @@ public final class TextNodeLayout: NSObject { for line in self.lines { lineIndex += 1 var lineFrame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: line.frame.origin.y - line.frame.size.height + self.firstLineOffset), size: line.frame.size) - switch self.alignment { + switch self.resolvedAlignment { case .center: lineFrame.origin.x = floor((self.size.width - lineFrame.size.width) / 2.0) case .natural: @@ -383,7 +385,7 @@ public final class TextNodeLayout: NSObject { for line in self.lines { lineIndex += 1 var lineFrame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: line.frame.origin.y - line.frame.size.height + self.firstLineOffset), size: line.frame.size) - switch self.alignment { + switch self.resolvedAlignment { case .center: lineFrame.origin.x = floor((self.size.width - lineFrame.size.width) / 2.0) case .natural: @@ -823,17 +825,30 @@ public class TextNode: ASDisplayNode { private class func calculateLayout(attributedString: NSAttributedString?, minimumNumberOfLines: Int, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, backgroundColor: UIColor?, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacingFactor: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, lineColor: UIColor?, textShadowColor: UIColor?, textStroke: (UIColor, CGFloat)?) -> TextNodeLayout { if let attributedString = attributedString { + let stringLength = attributedString.length let font: CTFont + let resolvedAlignment: NSTextAlignment + if stringLength != 0 { if let stringFont = attributedString.attribute(NSAttributedString.Key.font, at: 0, effectiveRange: nil) { font = stringFont as! CTFont } else { font = defaultFont } + if alignment == .center { + resolvedAlignment = .center + } else { + if let paragraphStyle = attributedString.attribute(NSAttributedString.Key.paragraphStyle, at: 0, effectiveRange: nil) as? NSParagraphStyle { + resolvedAlignment = paragraphStyle.alignment + } else { + resolvedAlignment = alignment + } + } } else { font = defaultFont + resolvedAlignment = alignment } let fontAscent = CTFontGetAscent(font) @@ -847,7 +862,7 @@ public class TextNode: ASDisplayNode { var maybeTypesetter: CTTypesetter? maybeTypesetter = CTTypesetterCreateWithAttributedString(attributedString as CFAttributedString) if maybeTypesetter == nil { - return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), rawTextSize: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textStroke: textStroke) + return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, explicitAlignment: alignment, resolvedAlignment: resolvedAlignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), rawTextSize: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textStroke: textStroke) } let typesetter = maybeTypesetter! @@ -1056,9 +1071,9 @@ public class TextNode: ASDisplayNode { } } - return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(width: ceil(layoutSize.width) + insets.left + insets.right, height: ceil(layoutSize.height) + insets.top + insets.bottom), rawTextSize: CGSize(width: ceil(rawLayoutSize.width) + insets.left + insets.right, height: ceil(rawLayoutSize.height) + insets.top + insets.bottom), truncated: truncated, firstLineOffset: firstLineOffset, lines: lines, blockQuotes: blockQuotes, backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textStroke: textStroke) + return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, explicitAlignment: alignment, resolvedAlignment: resolvedAlignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(width: ceil(layoutSize.width) + insets.left + insets.right, height: ceil(layoutSize.height) + insets.top + insets.bottom), rawTextSize: CGSize(width: ceil(rawLayoutSize.width) + insets.left + insets.right, height: ceil(rawLayoutSize.height) + insets.top + insets.bottom), truncated: truncated, firstLineOffset: firstLineOffset, lines: lines, blockQuotes: blockQuotes, backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textStroke: textStroke) } else { - return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), rawTextSize: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textStroke: textStroke) + return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, explicitAlignment: alignment, resolvedAlignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), rawTextSize: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textStroke: textStroke) } } @@ -1110,7 +1125,7 @@ public class TextNode: ASDisplayNode { let textPosition = context.textPosition context.textMatrix = CGAffineTransform(scaleX: 1.0, y: -1.0) - let alignment = layout.alignment + let alignment = layout.resolvedAlignment let offset = CGPoint(x: layout.insets.left, y: layout.insets.top) for i in 0 ..< layout.lines.count { @@ -1187,7 +1202,7 @@ public class TextNode: ASDisplayNode { let layout: TextNodeLayout var updated = false - if let existingLayout = existingLayout, existingLayout.constrainedSize == arguments.constrainedSize && existingLayout.maximumNumberOfLines == arguments.maximumNumberOfLines && existingLayout.truncationType == arguments.truncationType && existingLayout.cutout == arguments.cutout && existingLayout.alignment == arguments.alignment && existingLayout.lineSpacing.isEqual(to: arguments.lineSpacing) { + if let existingLayout = existingLayout, existingLayout.constrainedSize == arguments.constrainedSize && existingLayout.maximumNumberOfLines == arguments.maximumNumberOfLines && existingLayout.truncationType == arguments.truncationType && existingLayout.cutout == arguments.cutout && existingLayout.explicitAlignment == arguments.alignment && existingLayout.lineSpacing.isEqual(to: arguments.lineSpacing) { let stringMatch: Bool var colorMatch: Bool = true diff --git a/submodules/Display/Display/Toolbar.swift b/submodules/Display/Source/Toolbar.swift similarity index 100% rename from submodules/Display/Display/Toolbar.swift rename to submodules/Display/Source/Toolbar.swift diff --git a/submodules/Display/Display/ToolbarNode.swift b/submodules/Display/Source/ToolbarNode.swift similarity index 100% rename from submodules/Display/Display/ToolbarNode.swift rename to submodules/Display/Source/ToolbarNode.swift diff --git a/submodules/Display/Display/TooltipController.swift b/submodules/Display/Source/TooltipController.swift similarity index 100% rename from submodules/Display/Display/TooltipController.swift rename to submodules/Display/Source/TooltipController.swift diff --git a/submodules/Display/Display/TooltipControllerNode.swift b/submodules/Display/Source/TooltipControllerNode.swift similarity index 100% rename from submodules/Display/Display/TooltipControllerNode.swift rename to submodules/Display/Source/TooltipControllerNode.swift diff --git a/submodules/Display/Display/TransformImageArguments.swift b/submodules/Display/Source/TransformImageArguments.swift similarity index 100% rename from submodules/Display/Display/TransformImageArguments.swift rename to submodules/Display/Source/TransformImageArguments.swift diff --git a/submodules/Display/Display/TransformImageNode.swift b/submodules/Display/Source/TransformImageNode.swift similarity index 100% rename from submodules/Display/Display/TransformImageNode.swift rename to submodules/Display/Source/TransformImageNode.swift diff --git a/submodules/Display/Display/UIKitUtils.swift b/submodules/Display/Source/UIKitUtils.swift similarity index 95% rename from submodules/Display/Display/UIKitUtils.swift rename to submodules/Display/Source/UIKitUtils.swift index a8ad19277a..19ad82264e 100644 --- a/submodules/Display/Display/UIKitUtils.swift +++ b/submodules/Display/Source/UIKitUtils.swift @@ -1,4 +1,31 @@ import UIKit +import UIKitRuntimeUtils + +public extension UIView { + static func animationDurationFactor() -> Double { + return animationDurationFactorImpl() + } +} + +public func makeSpringAnimation(_ keyPath: String) -> CABasicAnimation { + return makeSpringAnimationImpl(keyPath) +} + +public func makeSpringBounceAnimation(_ keyPath: String, _ initialVelocity: CGFloat, _ damping: CGFloat) -> CABasicAnimation { + return makeSpringBounceAnimationImpl(keyPath, initialVelocity, damping) +} + +public func springAnimationValueAt(_ animation: CABasicAnimation, _ t: CGFloat) -> CGFloat { + return springAnimationValueAtImpl(animation, t) +} + +public func makeCustomZoomBlurEffect() -> UIBlurEffect? { + return makeCustomZoomBlurEffectImpl() +} + +public func applySmoothRoundedCorners(_ layer: CALayer) { + applySmoothRoundedCornersImpl(layer) +} public func dumpViews(_ view: UIView) { dumpViews(view, indent: "") diff --git a/submodules/Display/Display/UITracingLayerView.swift b/submodules/Display/Source/UITracingLayerView.swift similarity index 87% rename from submodules/Display/Display/UITracingLayerView.swift rename to submodules/Display/Source/UITracingLayerView.swift index 8a68530dea..f056baaa08 100644 --- a/submodules/Display/Display/UITracingLayerView.swift +++ b/submodules/Display/Source/UITracingLayerView.swift @@ -17,10 +17,6 @@ open class UITracingLayerView: UIView { } } - override open class var layerClass: AnyClass { - return CATracingLayer.self - } - override open func layoutSubviews() { super.layoutSubviews() diff --git a/submodules/Display/Source/UniversalMasterController.swift b/submodules/Display/Source/UniversalMasterController.swift new file mode 100644 index 0000000000..e69de29bb2 diff --git a/submodules/Display/Display/UniversalTapRecognizer.swift b/submodules/Display/Source/UniversalTapRecognizer.swift similarity index 100% rename from submodules/Display/Display/UniversalTapRecognizer.swift rename to submodules/Display/Source/UniversalTapRecognizer.swift diff --git a/submodules/Display/Display/ViewController.swift b/submodules/Display/Source/ViewController.swift similarity index 98% rename from submodules/Display/Display/ViewController.swift rename to submodules/Display/Source/ViewController.swift index 2b5a41b898..06fdcddc18 100644 --- a/submodules/Display/Display/ViewController.swift +++ b/submodules/Display/Source/ViewController.swift @@ -67,6 +67,12 @@ public enum ViewControllerNavigationPresentation { case modalInLargeLayout } +public enum TabBarItemContextActionType { + case none + case always + case whenActive +} + @objc open class ViewController: UIViewController, ContainableController { private var validLayout: ContainerViewLayout? public var currentlyAppliedLayout: ContainerViewLayout? { @@ -435,9 +441,6 @@ public enum ViewControllerNavigationPresentation { } open func displayNodeDidLoad() { - if let layer = self.displayNode.layer as? CATracingLayer { - layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: false, userData: self.displayNode.layer, tracingTag: WindowTracingTags.keyboard, disableChildrenTracingTags: 0)) - } self.updateScrollToTopView() if let backgroundColor = self.displayNode.backgroundColor, backgroundColor.alpha.isEqual(to: 1.0) { self.blocksBackgroundWhenInOverlay = true @@ -628,10 +631,13 @@ public enum ViewControllerNavigationPresentation { open func toolbarActionSelected(action: ToolbarActionOption) { } - open var hasTabBarItemContextAction: Bool = false + open var tabBarItemContextActionType: TabBarItemContextActionType = .none open func tabBarItemContextAction(sourceNode: ContextExtractedContentContainingNode, gesture: ContextGesture) { } + + open func tabBarItemSwipeAction(direction: TabBarItemSwipeDirection) { + } } func traceIsOpaque(layer: CALayer, rect: CGRect) -> Bool { diff --git a/submodules/Display/Display/ViewControllerPreviewing.swift b/submodules/Display/Source/ViewControllerPreviewing.swift similarity index 100% rename from submodules/Display/Display/ViewControllerPreviewing.swift rename to submodules/Display/Source/ViewControllerPreviewing.swift diff --git a/submodules/Display/Display/ViewControllerTracingNode.swift b/submodules/Display/Source/ViewControllerTracingNode.swift similarity index 100% rename from submodules/Display/Display/ViewControllerTracingNode.swift rename to submodules/Display/Source/ViewControllerTracingNode.swift diff --git a/submodules/Display/Display/VolumeControlStatusBar.swift b/submodules/Display/Source/VolumeControlStatusBar.swift similarity index 98% rename from submodules/Display/Display/VolumeControlStatusBar.swift rename to submodules/Display/Source/VolumeControlStatusBar.swift index 35501ba9d5..e498d4bda0 100644 --- a/submodules/Display/Display/VolumeControlStatusBar.swift +++ b/submodules/Display/Source/VolumeControlStatusBar.swift @@ -133,18 +133,15 @@ final class VolumeControlStatusBarNode: ASDisplayNode { self.outlineNode = ASImageNode() self.outlineNode.isLayerBacked = true self.outlineNode.displaysAsynchronously = false - self.outlineNode.displayWithoutProcessing = true self.backgroundNode = ASImageNode() self.backgroundNode.isLayerBacked = true self.backgroundNode.displaysAsynchronously = false - self.backgroundNode.displayWithoutProcessing = true self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 4.0, color: UIColor(rgb: 0xc5c5c5)) self.foregroundNode = ASImageNode() self.foregroundNode.isLayerBacked = true self.foregroundNode.displaysAsynchronously = false - self.foregroundNode.displayWithoutProcessing = true self.foregroundNode.image = generateStretchableFilledCircleImage(diameter: 4.0, color: .black) self.foregroundClippingNode = ASDisplayNode() @@ -154,7 +151,6 @@ final class VolumeControlStatusBarNode: ASDisplayNode { self.iconNode = ASImageNode() self.iconNode.isLayerBacked = true self.iconNode.displaysAsynchronously = false - self.iconNode.displayWithoutProcessing = true super.init() diff --git a/submodules/Display/Display/WallpaperBackgroundNode.swift b/submodules/Display/Source/WallpaperBackgroundNode.swift similarity index 100% rename from submodules/Display/Display/WallpaperBackgroundNode.swift rename to submodules/Display/Source/WallpaperBackgroundNode.swift diff --git a/submodules/Display/Display/WindowContent.swift b/submodules/Display/Source/WindowContent.swift similarity index 99% rename from submodules/Display/Display/WindowContent.swift rename to submodules/Display/Source/WindowContent.swift index cbf67d23e9..ba17a06b9a 100644 --- a/submodules/Display/Display/WindowContent.swift +++ b/submodules/Display/Source/WindowContent.swift @@ -204,11 +204,6 @@ public final class WindowHostView { } } -public struct WindowTracingTags { - public static let statusBar: Int32 = 1 << 0 - public static let keyboard: Int32 = 1 << 1 -} - public protocol WindowHost { func forEachController(_ f: (ContainableController) -> Void) func present(_ controller: ContainableController, on level: PresentationSurfaceLevel, blockInteraction: Bool, completion: @escaping () -> Void) @@ -366,10 +361,6 @@ public class Window1 { self?.updateSize(size, duration: duration) } - self.hostView.eventView.layer.setInvalidateTracingSublayers { [weak self] in - self?.invalidateTracingStatusBars() - } - self.hostView.layoutSubviews = { [weak self] in self?.layoutSubviews(force: false) } diff --git a/submodules/Display/Display/WindowCoveringView.swift b/submodules/Display/Source/WindowCoveringView.swift similarity index 100% rename from submodules/Display/Display/WindowCoveringView.swift rename to submodules/Display/Source/WindowCoveringView.swift diff --git a/submodules/Display/Display/WindowInputAccessoryHeightProvider.swift b/submodules/Display/Source/WindowInputAccessoryHeightProvider.swift similarity index 100% rename from submodules/Display/Display/WindowInputAccessoryHeightProvider.swift rename to submodules/Display/Source/WindowInputAccessoryHeightProvider.swift diff --git a/submodules/Display/Display/WindowPanRecognizer.swift b/submodules/Display/Source/WindowPanRecognizer.swift similarity index 100% rename from submodules/Display/Display/WindowPanRecognizer.swift rename to submodules/Display/Source/WindowPanRecognizer.swift diff --git a/submodules/Emoji/BUILD b/submodules/Emoji/BUILD new file mode 100644 index 0000000000..9b5e934bcf --- /dev/null +++ b/submodules/Emoji/BUILD @@ -0,0 +1,12 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "Emoji", + module_name = "Emoji", + srcs = glob([ + "Sources/**/*.swift", + ]), + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/Emoji/Info.plist b/submodules/Emoji/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/Emoji/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/Emoji/Sources/Emoji.h b/submodules/Emoji/Sources/Emoji.h deleted file mode 100644 index 5e70ebcc00..0000000000 --- a/submodules/Emoji/Sources/Emoji.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// Emoji.h -// Emoji -// -// Created by Peter on 8/15/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for Emoji. -FOUNDATION_EXPORT double EmojiVersionNumber; - -//! Project version string for Emoji. -FOUNDATION_EXPORT const unsigned char EmojiVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/EncryptionKeyVisualization/BUCK b/submodules/EncryptionKeyVisualization/BUCK index 8fa948603e..72ecc6e1db 100644 --- a/submodules/EncryptionKeyVisualization/BUCK +++ b/submodules/EncryptionKeyVisualization/BUCK @@ -4,17 +4,11 @@ static_library( name = "EncryptionKeyVisualization", srcs = glob([ "Sources/*.swift", - "Sources/*.m", ]), - headers = glob([ - "Sources/*.h", - ], exclude = ["Sources/EncryptionKeyVisualization.h"]), - exported_headers = glob([ - "Sources/*.h", - ], exclude = ["Sources/EncryptionKeyVisualization.h"]), deps = [ "//submodules/TelegramCore:TelegramCore#shared", "//submodules/SyncCore:SyncCore#shared", + "//submodules/EncryptionKeyVisualization/Impl:EncryptionKeyVisualizationImpl", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/EncryptionKeyVisualization/BUILD b/submodules/EncryptionKeyVisualization/BUILD new file mode 100644 index 0000000000..6717c60e4f --- /dev/null +++ b/submodules/EncryptionKeyVisualization/BUILD @@ -0,0 +1,17 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "EncryptionKeyVisualization", + module_name = "EncryptionKeyVisualization", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/TelegramCore:TelegramCore", + "//submodules/SyncCore:SyncCore", + "//submodules/EncryptionKeyVisualization/Impl:EncryptionKeyVisualizationImpl", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/EncryptionKeyVisualization/Impl/BUCK b/submodules/EncryptionKeyVisualization/Impl/BUCK new file mode 100644 index 0000000000..19d1462ea0 --- /dev/null +++ b/submodules/EncryptionKeyVisualization/Impl/BUCK @@ -0,0 +1,18 @@ +load("//Config:buck_rule_macros.bzl", "static_library") + +static_library( + name = "EncryptionKeyVisualizationImpl", + srcs = glob([ + "Sources/*.m", + ]), + headers = glob([ + "Sources/*.h", + ]), + exported_headers = glob([ + "PublicHeaders/**/*.h", + ]), + frameworks = [ + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + "$SDKROOT/System/Library/Frameworks/UIKit.framework", + ], +) diff --git a/submodules/EncryptionKeyVisualization/Impl/BUILD b/submodules/EncryptionKeyVisualization/Impl/BUILD new file mode 100644 index 0000000000..c4c7874874 --- /dev/null +++ b/submodules/EncryptionKeyVisualization/Impl/BUILD @@ -0,0 +1,22 @@ + +objc_library( + name = "EncryptionKeyVisualizationImpl", + enable_modules = True, + module_name = "EncryptionKeyVisualizationImpl", + srcs = glob([ + "Sources/**/*.m", + "Sources/**/*.h", + ]), + hdrs = glob([ + "PublicHeaders/**/*.h", + ]), + includes = [ + "PublicHeaders", + ], + sdk_frameworks = [ + "Foundation", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/EncryptionKeyVisualization/Impl/PublicHeaders/EncryptionKeyVisualizationImpl/EncryptionKeyVisualization.h b/submodules/EncryptionKeyVisualization/Impl/PublicHeaders/EncryptionKeyVisualizationImpl/EncryptionKeyVisualization.h new file mode 100644 index 0000000000..12ed60d6cb --- /dev/null +++ b/submodules/EncryptionKeyVisualization/Impl/PublicHeaders/EncryptionKeyVisualizationImpl/EncryptionKeyVisualization.h @@ -0,0 +1,5 @@ +#import + +#import + + diff --git a/submodules/EncryptionKeyVisualization/Sources/SecretChatKeyVisualization.h b/submodules/EncryptionKeyVisualization/Impl/PublicHeaders/EncryptionKeyVisualizationImpl/SecretChatKeyVisualization.h similarity index 100% rename from submodules/EncryptionKeyVisualization/Sources/SecretChatKeyVisualization.h rename to submodules/EncryptionKeyVisualization/Impl/PublicHeaders/EncryptionKeyVisualizationImpl/SecretChatKeyVisualization.h diff --git a/submodules/EncryptionKeyVisualization/Sources/SecretChatKeyVisualization.m b/submodules/EncryptionKeyVisualization/Impl/Sources/SecretChatKeyVisualization.m similarity index 98% rename from submodules/EncryptionKeyVisualization/Sources/SecretChatKeyVisualization.m rename to submodules/EncryptionKeyVisualization/Impl/Sources/SecretChatKeyVisualization.m index 50d100e6f9..466ded7470 100644 --- a/submodules/EncryptionKeyVisualization/Sources/SecretChatKeyVisualization.m +++ b/submodules/EncryptionKeyVisualization/Impl/Sources/SecretChatKeyVisualization.m @@ -1,4 +1,4 @@ -#import "SecretChatKeyVisualization.h" +#import #import diff --git a/submodules/EncryptionKeyVisualization/Info.plist b/submodules/EncryptionKeyVisualization/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/EncryptionKeyVisualization/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/EncryptionKeyVisualization/Sources/EncryptionKeyVisualization.h b/submodules/EncryptionKeyVisualization/Sources/EncryptionKeyVisualization.h deleted file mode 100644 index f4cfd58533..0000000000 --- a/submodules/EncryptionKeyVisualization/Sources/EncryptionKeyVisualization.h +++ /dev/null @@ -1,11 +0,0 @@ -#import - -//! Project version number for EncryptionKeyVisualization. -FOUNDATION_EXPORT double EncryptionKeyVisualizationVersionNumber; - -//! Project version string for EncryptionKeyVisualization. -FOUNDATION_EXPORT const unsigned char EncryptionKeyVisualizationVersionString[]; - -#import - - diff --git a/submodules/EncryptionKeyVisualization/Sources/SecretChatKeyVisualization.swift b/submodules/EncryptionKeyVisualization/Sources/SecretChatKeyVisualization.swift index cf974f1843..90a83ea5a9 100644 --- a/submodules/EncryptionKeyVisualization/Sources/SecretChatKeyVisualization.swift +++ b/submodules/EncryptionKeyVisualization/Sources/SecretChatKeyVisualization.swift @@ -1,6 +1,8 @@ import Foundation +import UIKit import TelegramCore import SyncCore +import EncryptionKeyVisualizationImpl public func secretChatKeyImage(_ fingerprint: SecretChatKeyFingerprint, size: CGSize) -> UIImage? { let keySignatureData = fingerprint.sha1.data() diff --git a/submodules/EncryptionProvider/BUCK b/submodules/EncryptionProvider/BUCK index d05c62c62f..c99c089695 100644 --- a/submodules/EncryptionProvider/BUCK +++ b/submodules/EncryptionProvider/BUCK @@ -6,10 +6,10 @@ static_library( "Sources/**/*.m", ]), headers = glob([ - "Sources/**/*.h", + "PublicHeaders/**/*.h", ]), exported_headers = glob([ - "Sources/**/*.h", + "PublicHeaders/**/*.h", ]), deps = [ ], diff --git a/submodules/EncryptionProvider/BUILD b/submodules/EncryptionProvider/BUILD new file mode 100644 index 0000000000..7b876fc0d7 --- /dev/null +++ b/submodules/EncryptionProvider/BUILD @@ -0,0 +1,18 @@ + +objc_library( + name = "EncryptionProvider", + enable_modules = True, + module_name = "EncryptionProvider", + srcs = glob([ + "Sources/**/*.m", + ]), + hdrs = glob([ + "PublicHeaders/**/*.h", + ]), + includes = [ + "PublicHeaders", + ], + deps = [ + ], + visibility = ["//visibility:public"], +) diff --git a/submodules/EncryptionProvider/Sources/EncryptionProvider.h b/submodules/EncryptionProvider/PublicHeaders/EncryptionProvider/EncryptionProvider.h similarity index 100% rename from submodules/EncryptionProvider/Sources/EncryptionProvider.h rename to submodules/EncryptionProvider/PublicHeaders/EncryptionProvider/EncryptionProvider.h diff --git a/submodules/FFMpegBinding/BUCK b/submodules/FFMpegBinding/BUCK new file mode 100644 index 0000000000..b011e6c299 --- /dev/null +++ b/submodules/FFMpegBinding/BUCK @@ -0,0 +1,20 @@ +load("//Config:buck_rule_macros.bzl", "static_library") + +static_library( + name = "FFMpegBinding", + srcs = glob([ + "Sources/*.m", + ]), + headers = glob([ + "Sources/*.h", + ]), + exported_headers = glob([ + "Public/**/*.h", + ]), + deps = [ + "//submodules/ffmpeg:ffmpeg", + ], + frameworks = [ + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + ], +) diff --git a/submodules/FFMpegBinding/BUILD b/submodules/FFMpegBinding/BUILD new file mode 100644 index 0000000000..094b363bd2 --- /dev/null +++ b/submodules/FFMpegBinding/BUILD @@ -0,0 +1,22 @@ + +objc_library( + name = "FFMpegBinding", + module_name = "FFMpegBinding", + enable_modules = True, + srcs = glob([ + "Sources/**/*.m", + "Sources/**/*.h", + ]), + hdrs = glob([ + "Public/**/*.h", + ]), + includes = [ + "Public", + ], + deps = [ + "//submodules/ffmpeg:ffmpeg", + ], + visibility = [ + "//visibility:public", + ] +) diff --git a/submodules/ffmpeg/FFMpeg/FFMpegAVCodec.h b/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegAVCodec.h similarity index 100% rename from submodules/ffmpeg/FFMpeg/FFMpegAVCodec.h rename to submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegAVCodec.h diff --git a/submodules/ffmpeg/FFMpeg/FFMpegAVCodecContext.h b/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegAVCodecContext.h similarity index 89% rename from submodules/ffmpeg/FFMpeg/FFMpegAVCodecContext.h rename to submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegAVCodecContext.h index 3bc227a3aa..92a2c78481 100644 --- a/submodules/ffmpeg/FFMpeg/FFMpegAVCodecContext.h +++ b/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegAVCodecContext.h @@ -1,6 +1,6 @@ #import -#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/submodules/ffmpeg/FFMpeg/FFMpegAVFormatContext.h b/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegAVFormatContext.h similarity index 100% rename from submodules/ffmpeg/FFMpeg/FFMpegAVFormatContext.h rename to submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegAVFormatContext.h diff --git a/submodules/ffmpeg/FFMpeg/FFMpegAVFrame.h b/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegAVFrame.h similarity index 100% rename from submodules/ffmpeg/FFMpeg/FFMpegAVFrame.h rename to submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegAVFrame.h diff --git a/submodules/ffmpeg/FFMpeg/FFMpegAVIOContext.h b/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegAVIOContext.h similarity index 100% rename from submodules/ffmpeg/FFMpeg/FFMpegAVIOContext.h rename to submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegAVIOContext.h diff --git a/submodules/ffmpeg/FFMpeg/FFMpegAVSampleFormat.h b/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegAVSampleFormat.h similarity index 100% rename from submodules/ffmpeg/FFMpeg/FFMpegAVSampleFormat.h rename to submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegAVSampleFormat.h diff --git a/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegBinding.h b/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegBinding.h new file mode 100644 index 0000000000..565c6c170a --- /dev/null +++ b/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegBinding.h @@ -0,0 +1,12 @@ +#import + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import diff --git a/submodules/ffmpeg/FFMpeg/FFMpegGlobals.h b/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegGlobals.h similarity index 100% rename from submodules/ffmpeg/FFMpeg/FFMpegGlobals.h rename to submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegGlobals.h diff --git a/submodules/ffmpeg/FFMpeg/FFMpegPacket.h b/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegPacket.h similarity index 100% rename from submodules/ffmpeg/FFMpeg/FFMpegPacket.h rename to submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegPacket.h diff --git a/submodules/ffmpeg/FFMpeg/FFMpegRemuxer.h b/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegRemuxer.h similarity index 100% rename from submodules/ffmpeg/FFMpeg/FFMpegRemuxer.h rename to submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegRemuxer.h diff --git a/submodules/ffmpeg/FFMpeg/FFMpegSWResample.h b/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegSWResample.h similarity index 92% rename from submodules/ffmpeg/FFMpeg/FFMpegSWResample.h rename to submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegSWResample.h index 2228f2bd8c..3b56a9c8ad 100644 --- a/submodules/ffmpeg/FFMpeg/FFMpegSWResample.h +++ b/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegSWResample.h @@ -1,6 +1,6 @@ #import -#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/submodules/ffmpeg/FFMpeg/FFMpegAVCodec.m b/submodules/FFMpegBinding/Sources/FFMpegAVCodec.m similarity index 93% rename from submodules/ffmpeg/FFMpeg/FFMpegAVCodec.m rename to submodules/FFMpegBinding/Sources/FFMpegAVCodec.m index 86cab534a2..857462cfec 100644 --- a/submodules/ffmpeg/FFMpeg/FFMpegAVCodec.m +++ b/submodules/FFMpegBinding/Sources/FFMpegAVCodec.m @@ -1,4 +1,4 @@ -#import "FFMpegAVCodec.h" +#import #import "libavcodec/avcodec.h" diff --git a/submodules/ffmpeg/FFMpeg/FFMpegAVCodecContext.m b/submodules/FFMpegBinding/Sources/FFMpegAVCodecContext.m similarity index 89% rename from submodules/ffmpeg/FFMpeg/FFMpegAVCodecContext.m rename to submodules/FFMpegBinding/Sources/FFMpegAVCodecContext.m index 5d7ff9f9f6..5f63c84f80 100644 --- a/submodules/ffmpeg/FFMpeg/FFMpegAVCodecContext.m +++ b/submodules/FFMpegBinding/Sources/FFMpegAVCodecContext.m @@ -1,7 +1,7 @@ -#import "FFMpegAVCodecContext.h" +#import -#import "FFMpegAVFrame.h" -#import "FFMpegAVCodec.h" +#import +#import #import "libavcodec/avcodec.h" diff --git a/submodules/ffmpeg/FFMpeg/FFMpegAVFormatContext.m b/submodules/FFMpegBinding/Sources/FFMpegAVFormatContext.m similarity index 96% rename from submodules/ffmpeg/FFMpeg/FFMpegAVFormatContext.m rename to submodules/FFMpegBinding/Sources/FFMpegAVFormatContext.m index 45b732b4ec..5d91d6b080 100644 --- a/submodules/ffmpeg/FFMpeg/FFMpegAVFormatContext.m +++ b/submodules/FFMpegBinding/Sources/FFMpegAVFormatContext.m @@ -1,8 +1,8 @@ -#import "FFMpegAVFormatContext.h" +#import -#import "FFMpegAVIOContext.h" -#import "FFMpegPacket.h" -#import "FFMpegAVCodecContext.h" +#import +#import +#import #import "libavformat/avformat.h" diff --git a/submodules/ffmpeg/FFMpeg/FFMpegAVFrame.m b/submodules/FFMpegBinding/Sources/FFMpegAVFrame.m similarity index 95% rename from submodules/ffmpeg/FFMpeg/FFMpegAVFrame.m rename to submodules/FFMpegBinding/Sources/FFMpegAVFrame.m index fd1fcb203a..2598869967 100644 --- a/submodules/ffmpeg/FFMpeg/FFMpegAVFrame.m +++ b/submodules/FFMpegBinding/Sources/FFMpegAVFrame.m @@ -1,4 +1,4 @@ -#import "FFMpegAVFrame.h" +#import #import "libavformat/avformat.h" diff --git a/submodules/ffmpeg/FFMpeg/FFMpegAVIOContext.m b/submodules/FFMpegBinding/Sources/FFMpegAVIOContext.m similarity index 96% rename from submodules/ffmpeg/FFMpeg/FFMpegAVIOContext.m rename to submodules/FFMpegBinding/Sources/FFMpegAVIOContext.m index cdc46664f0..8f76b54667 100644 --- a/submodules/ffmpeg/FFMpeg/FFMpegAVIOContext.m +++ b/submodules/FFMpegBinding/Sources/FFMpegAVIOContext.m @@ -1,4 +1,4 @@ -#import "FFMpegAVIOContext.h" +#import #import "libavformat/avformat.h" diff --git a/submodules/ffmpeg/FFMpeg/FFMpegGlobals.m b/submodules/FFMpegBinding/Sources/FFMpegGlobals.m similarity index 84% rename from submodules/ffmpeg/FFMpeg/FFMpegGlobals.m rename to submodules/FFMpegBinding/Sources/FFMpegGlobals.m index c507fff969..8729f8a3a9 100644 --- a/submodules/ffmpeg/FFMpeg/FFMpegGlobals.m +++ b/submodules/FFMpegBinding/Sources/FFMpegGlobals.m @@ -1,4 +1,4 @@ -#import "FFMpegGlobals.h" +#import #import "libavformat/avformat.h" diff --git a/submodules/ffmpeg/FFMpeg/FFMpegPacket.m b/submodules/FFMpegBinding/Sources/FFMpegPacket.m similarity index 91% rename from submodules/ffmpeg/FFMpeg/FFMpegPacket.m rename to submodules/FFMpegBinding/Sources/FFMpegPacket.m index 902348ea8d..bf647d2370 100644 --- a/submodules/ffmpeg/FFMpeg/FFMpegPacket.m +++ b/submodules/FFMpegBinding/Sources/FFMpegPacket.m @@ -1,6 +1,6 @@ -#import "FFMpegPacket.h" +#import -#import "FFMpegAVCodecContext.h" +#import #import "libavformat/avformat.h" diff --git a/submodules/ffmpeg/FFMpeg/FFMpegRemuxer.m b/submodules/FFMpegBinding/Sources/FFMpegRemuxer.m similarity index 99% rename from submodules/ffmpeg/FFMpeg/FFMpegRemuxer.m rename to submodules/FFMpegBinding/Sources/FFMpegRemuxer.m index 7fcb91ecdf..b25f557222 100644 --- a/submodules/ffmpeg/FFMpeg/FFMpegRemuxer.m +++ b/submodules/FFMpegBinding/Sources/FFMpegRemuxer.m @@ -1,6 +1,6 @@ -#import "FFMpegRemuxer.h" +#import -#import "FFMpegAVIOContext.h" +#import #include "libavutil/timestamp.h" #include "libavformat/avformat.h" diff --git a/submodules/ffmpeg/FFMpeg/FFMpegSWResample.m b/submodules/FFMpegBinding/Sources/FFMpegSWResample.m similarity index 97% rename from submodules/ffmpeg/FFMpeg/FFMpegSWResample.m rename to submodules/FFMpegBinding/Sources/FFMpegSWResample.m index 6faf60755f..3afe624447 100644 --- a/submodules/ffmpeg/FFMpeg/FFMpegSWResample.m +++ b/submodules/FFMpegBinding/Sources/FFMpegSWResample.m @@ -1,6 +1,6 @@ -#import "FFMpegSWResample.h" +#import -#import "FFMpegAVFrame.h" +#import #import "libavcodec/avcodec.h" #import "libswresample/swresample.h" diff --git a/submodules/FastBlur/BUCK b/submodules/FastBlur/BUCK new file mode 100644 index 0000000000..5e2cc7b602 --- /dev/null +++ b/submodules/FastBlur/BUCK @@ -0,0 +1,19 @@ +load("//Config:buck_rule_macros.bzl", "static_library") + +static_library( + name = "FastBlur", + srcs = glob([ + "Sources/*.m", + ]), + headers = glob([ + "Sources/*.h", + ]), + exported_headers = glob([ + "PublicHeaders/**/*.h", + ]), + deps = [ + ], + frameworks = [ + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + ], +) diff --git a/submodules/FastBlur/BUILD b/submodules/FastBlur/BUILD new file mode 100644 index 0000000000..839f88d8c1 --- /dev/null +++ b/submodules/FastBlur/BUILD @@ -0,0 +1,23 @@ + +objc_library( + name = "FastBlur", + enable_modules = True, + module_name = "FastBlur", + srcs = glob([ + "Sources/*.m", + ]), + hdrs = glob([ + "PublicHeaders/**/*.h", + ]), + includes = [ + "PublicHeaders", + ], + sdk_frameworks = [ + "Foundation", + "UIKit", + "Accelerate", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/ImageBlur/Sources/ApplyScreenshotEffect.h b/submodules/FastBlur/PublicHeaders/FastBlur/ApplyScreenshotEffect.h similarity index 100% rename from submodules/ImageBlur/Sources/ApplyScreenshotEffect.h rename to submodules/FastBlur/PublicHeaders/FastBlur/ApplyScreenshotEffect.h diff --git a/submodules/ImageBlur/Sources/FastBlur.h b/submodules/FastBlur/PublicHeaders/FastBlur/FastBlur.h similarity index 91% rename from submodules/ImageBlur/Sources/FastBlur.h rename to submodules/FastBlur/PublicHeaders/FastBlur/FastBlur.h index 1a06dcc567..55830298bc 100644 --- a/submodules/ImageBlur/Sources/FastBlur.h +++ b/submodules/FastBlur/PublicHeaders/FastBlur/FastBlur.h @@ -3,6 +3,8 @@ #import +#import + void imageFastBlur(int imageWidth, int imageHeight, int imageStride, void * _Nonnull pixels); void telegramFastBlurMore(int imageWidth, int imageHeight, int imageStride, void * _Nonnull pixels); void stickerThumbnailAlphaBlur(int imageWidth, int imageHeight, int imageStride, void * _Nonnull pixels); diff --git a/submodules/ImageBlur/Sources/ApplyScreenshotEffect.m b/submodules/FastBlur/Sources/ApplyScreenshotEffect.m similarity index 99% rename from submodules/ImageBlur/Sources/ApplyScreenshotEffect.m rename to submodules/FastBlur/Sources/ApplyScreenshotEffect.m index 8cfd8109b5..6ab0275e44 100644 --- a/submodules/ImageBlur/Sources/ApplyScreenshotEffect.m +++ b/submodules/FastBlur/Sources/ApplyScreenshotEffect.m @@ -1,4 +1,4 @@ -#import "ApplyScreenshotEffect.h" +#import #import #import diff --git a/submodules/ImageBlur/Sources/FastBlur.m b/submodules/FastBlur/Sources/FastBlur.m similarity index 99% rename from submodules/ImageBlur/Sources/FastBlur.m rename to submodules/FastBlur/Sources/FastBlur.m index 106e50b545..2c7849384f 100644 --- a/submodules/ImageBlur/Sources/FastBlur.m +++ b/submodules/FastBlur/Sources/FastBlur.m @@ -1,4 +1,4 @@ -#import "FastBlur.h" +#import #import diff --git a/submodules/GZip/BUILD b/submodules/GZip/BUILD new file mode 100644 index 0000000000..b94e8bfd2d --- /dev/null +++ b/submodules/GZip/BUILD @@ -0,0 +1,18 @@ + +objc_library( + name = "GZip", + enable_modules = True, + module_name = "GZip", + srcs = glob([ + "Sources/**/*.m", + ]), + hdrs = glob([ + "Sources/**/*.h", + ]), + sdk_dylibs = [ + "libz", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/GZip/Info.plist b/submodules/GZip/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/GZip/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/GalleryUI/BUILD b/submodules/GalleryUI/BUILD new file mode 100644 index 0000000000..ad5f85f26d --- /dev/null +++ b/submodules/GalleryUI/BUILD @@ -0,0 +1,34 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "GalleryUI", + module_name = "GalleryUI", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/SyncCore:SyncCore", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/TextFormat:TextFormat", + "//submodules/AccountContext:AccountContext", + "//submodules/TelegramUniversalVideoContent:TelegramUniversalVideoContent", + "//submodules/WebsiteType:WebsiteType", + "//submodules/ScreenCaptureDetection:ScreenCaptureDetection", + "//submodules/OpenInExternalAppUI:OpenInExternalAppUI", + "//submodules/ShareController:ShareController", + "//submodules/SwipeToDismissGesture:SwipeToDismissGesture", + "//submodules/CheckNode:CheckNode", + "//submodules/AppBundle:AppBundle", + "//submodules/StickerPackPreviewUI:StickerPackPreviewUI", + "//submodules/OverlayStatusController:OverlayStatusController", + "//submodules/PresentationDataUtils:PresentationDataUtils", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/GalleryUI/Info.plist b/submodules/GalleryUI/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/GalleryUI/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/GalleryUI/Sources/GalleryPagerNode.swift b/submodules/GalleryUI/Sources/GalleryPagerNode.swift index edae0ad81f..d07e240f34 100644 --- a/submodules/GalleryUI/Sources/GalleryPagerNode.swift +++ b/submodules/GalleryUI/Sources/GalleryPagerNode.swift @@ -5,7 +5,9 @@ import Display import SwiftSignalKit import Postbox -private let edgeWidth: CGFloat = 44.0 +private func edgeWidth(width: CGFloat) -> CGFloat { + return min(44.0, floor(width / 6.0)) +} private let leftFadeImage = generateImage(CGSize(width: 64.0, height: 1.0), opaque: false, rotatedContext: { size, context in let bounds = CGRect(origin: CGPoint(), size: size) @@ -153,11 +155,11 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest let size = strongSelf.bounds var highlightedSide: Bool? - if point.x < edgeWidth && strongSelf.canGoToPreviousItem() { + if point.x < edgeWidth(width: size.width) && strongSelf.canGoToPreviousItem() { if strongSelf.items.count > 1 { highlightedSide = false } - } else if point.x > size.width - edgeWidth && strongSelf.canGoToNextItem() { + } else if point.x > size.width - edgeWidth(width: size.width) && strongSelf.canGoToNextItem() { if strongSelf.items.count > 1 { highlightedSide = true } @@ -180,11 +182,11 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest var highlightedSide: Bool? if let point = point { - if point.x < edgeWidth && strongSelf.canGoToPreviousItem() { + if point.x < edgeWidth(width: size.width) && strongSelf.canGoToPreviousItem() { if strongSelf.items.count > 1 { highlightedSide = false } - } else if point.x > size.width - edgeWidth && strongSelf.canGoToNextItem() { + } else if point.x > size.width - edgeWidth(width: size.width) && strongSelf.canGoToNextItem() { if strongSelf.items.count > 1 { highlightedSide = true } @@ -233,9 +235,9 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation { if case .tap = gesture { let size = self.bounds.size - if location.x < edgeWidth && self.canGoToPreviousItem() { + if location.x < edgeWidth(width: size.width) && self.canGoToPreviousItem() { self.goToPreviousItem() - } else if location.x > size.width - edgeWidth && self.canGoToNextItem() { + } else if location.x > size.width - edgeWidth(width: size.width) && self.canGoToNextItem() { self.goToNextItem() } } diff --git a/submodules/GalleryUI/Sources/GalleryUI.h b/submodules/GalleryUI/Sources/GalleryUI.h deleted file mode 100644 index 96745aa2f0..0000000000 --- a/submodules/GalleryUI/Sources/GalleryUI.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// GalleryUI.h -// GalleryUI -// -// Created by Peter on 8/11/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for GalleryUI. -FOUNDATION_EXPORT double GalleryUIVersionNumber; - -//! Project version string for GalleryUI. -FOUNDATION_EXPORT const unsigned char GalleryUIVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/GameUI/BUILD b/submodules/GameUI/BUILD new file mode 100644 index 0000000000..95d7034d1b --- /dev/null +++ b/submodules/GameUI/BUILD @@ -0,0 +1,23 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "GameUI", + module_name = "GameUI", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/SyncCore:SyncCore", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/AccountContext:AccountContext", + "//submodules/ShareController:ShareController", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/GameUI/Info.plist b/submodules/GameUI/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/GameUI/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/GameUI/Sources/GameUI.h b/submodules/GameUI/Sources/GameUI.h deleted file mode 100644 index 18da9568d0..0000000000 --- a/submodules/GameUI/Sources/GameUI.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// GameUI.h -// GameUI -// -// Created by Peter on 8/11/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for GameUI. -FOUNDATION_EXPORT double GameUIVersionNumber; - -//! Project version string for GameUI. -FOUNDATION_EXPORT const unsigned char GameUIVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/Geocoding/BUILD b/submodules/Geocoding/BUILD new file mode 100644 index 0000000000..d03fe14734 --- /dev/null +++ b/submodules/Geocoding/BUILD @@ -0,0 +1,15 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "Geocoding", + module_name = "Geocoding", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/Geocoding/Info.plist b/submodules/Geocoding/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/Geocoding/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/Geocoding/Sources/Geocoding.h b/submodules/Geocoding/Sources/Geocoding.h deleted file mode 100644 index d49cdcfd42..0000000000 --- a/submodules/Geocoding/Sources/Geocoding.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// Geocoding.h -// Geocoding -// -// Created by Peter on 8/15/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for Geocoding. -FOUNDATION_EXPORT double GeocodingVersionNumber; - -//! Project version string for Geocoding. -FOUNDATION_EXPORT const unsigned char GeocodingVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/GlassButtonNode/BUILD b/submodules/GlassButtonNode/BUILD new file mode 100644 index 0000000000..7a35f60ab4 --- /dev/null +++ b/submodules/GlassButtonNode/BUILD @@ -0,0 +1,16 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "GlassButtonNode", + module_name = "GlassButtonNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/GlassButtonNode/Sources/GlassButtonNode.swift b/submodules/GlassButtonNode/Sources/GlassButtonNode.swift index f9fd932c6a..285964e124 100644 --- a/submodules/GlassButtonNode/Sources/GlassButtonNode.swift +++ b/submodules/GlassButtonNode/Sources/GlassButtonNode.swift @@ -72,7 +72,7 @@ public final class GlassButtonNode: HighlightTrackingButtonNode { private let blurView: UIVisualEffectView private let iconNode: ASImageNode - private var labelNode: ASTextNode? + private var labelNode: ImmediateTextNode? public init(icon: UIImage, label: String?) { let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .light)) @@ -90,7 +90,7 @@ public final class GlassButtonNode: HighlightTrackingButtonNode { self.filledImage = generateEmptyButtonImage(icon: icon, strokeColor: nil, fillColor: invertedFill, knockout: true, buttonSize: largeButtonSize) if let label = label { - let labelNode = ASTextNode() + let labelNode = ImmediateTextNode() let labelFont: UIFont if let image = regularImage, image.size.width < 70.0 { labelFont = smallLabelFont @@ -165,7 +165,7 @@ public final class GlassButtonNode: HighlightTrackingButtonNode { self.iconNode.frame = self.bounds if let labelNode = self.labelNode { - let labelSize = labelNode.measure(CGSize(width: 200.0, height: 100.0)) + let labelSize = labelNode.updateLayout(CGSize(width: 200.0, height: 100.0)) let offset: CGFloat if size.width < 70.0 { offset = 65.0 diff --git a/submodules/GraphCore/BUILD b/submodules/GraphCore/BUILD new file mode 100644 index 0000000000..10c347161d --- /dev/null +++ b/submodules/GraphCore/BUILD @@ -0,0 +1,15 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "GraphCore", + module_name = "GraphCore", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/Display:Display", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/GraphUI/BUILD b/submodules/GraphUI/BUILD new file mode 100644 index 0000000000..c815a189db --- /dev/null +++ b/submodules/GraphUI/BUILD @@ -0,0 +1,27 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "GraphUI", + module_name = "GraphUI", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/SyncCore:SyncCore", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/TelegramUIPreferences:TelegramUIPreferences", + "//submodules/AccountContext:AccountContext", + "//submodules/TelegramStringFormatting:TelegramStringFormatting", + "//submodules/PresentationDataUtils:PresentationDataUtils", + "//submodules/AppBundle:AppBundle", + "//submodules/GraphCore:GraphCore", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/GridMessageSelectionNode/BUILD b/submodules/GridMessageSelectionNode/BUILD new file mode 100644 index 0000000000..1d24313555 --- /dev/null +++ b/submodules/GridMessageSelectionNode/BUILD @@ -0,0 +1,18 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "GridMessageSelectionNode", + module_name = "GridMessageSelectionNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/CheckNode:CheckNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/GridMessageSelectionNode/Info.plist b/submodules/GridMessageSelectionNode/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/GridMessageSelectionNode/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/GridMessageSelectionNode/Sources/GridMessageSelectionNode.h b/submodules/GridMessageSelectionNode/Sources/GridMessageSelectionNode.h deleted file mode 100644 index 7bc2834b05..0000000000 --- a/submodules/GridMessageSelectionNode/Sources/GridMessageSelectionNode.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// GridMessageSelectionNode.h -// GridMessageSelectionNode -// -// Created by Peter on 8/17/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for GridMessageSelectionNode. -FOUNDATION_EXPORT double GridMessageSelectionNodeVersionNumber; - -//! Project version string for GridMessageSelectionNode. -FOUNDATION_EXPORT const unsigned char GridMessageSelectionNodeVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/HashtagSearchUI/BUILD b/submodules/HashtagSearchUI/BUILD new file mode 100644 index 0000000000..0384121b97 --- /dev/null +++ b/submodules/HashtagSearchUI/BUILD @@ -0,0 +1,25 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "HashtagSearchUI", + module_name = "HashtagSearchUI", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/SyncCore:SyncCore", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/AccountContext:AccountContext", + "//submodules/TelegramBaseController:TelegramBaseController", + "//submodules/ChatListUI:ChatListUI", + "//submodules/SegmentedControlNode:SegmentedControlNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/HashtagSearchUI/Info.plist b/submodules/HashtagSearchUI/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/HashtagSearchUI/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift index f9675922f4..3ada19008a 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift @@ -51,6 +51,7 @@ public final class HashtagSearchController: TelegramBaseController { }, peerSelected: { peer in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in + }, additionalCategorySelected: { _ in }, messageSelected: { [weak self] peer, message, _ in if let strongSelf = self { strongSelf.openMessageFromSearchDisposable.set((storedMessageFromSearchPeer(account: strongSelf.context.account, peer: peer) |> deliverOnMainQueue).start(next: { actualPeerId in diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchUI.h b/submodules/HashtagSearchUI/Sources/HashtagSearchUI.h deleted file mode 100644 index c5be233f9a..0000000000 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchUI.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// HashtagSearchUI.h -// HashtagSearchUI -// -// Created by Peter on 8/14/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for HashtagSearchUI. -FOUNDATION_EXPORT double HashtagSearchUIVersionNumber; - -//! Project version string for HashtagSearchUI. -FOUNDATION_EXPORT const unsigned char HashtagSearchUIVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/HexColor/BUILD b/submodules/HexColor/BUILD new file mode 100644 index 0000000000..faf074d046 --- /dev/null +++ b/submodules/HexColor/BUILD @@ -0,0 +1,15 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "HexColor", + module_name = "HexColor", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/TextFormat:TextFormat", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/HexColor/Info.plist b/submodules/HexColor/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/HexColor/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/HexColor/Sources/HexColor.h b/submodules/HexColor/Sources/HexColor.h deleted file mode 100644 index add4edae03..0000000000 --- a/submodules/HexColor/Sources/HexColor.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// HexColor.h -// HexColor -// -// Created by Peter on 8/17/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for HexColor. -FOUNDATION_EXPORT double HexColorVersionNumber; - -//! Project version string for HexColor. -FOUNDATION_EXPORT const unsigned char HexColorVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/HockeySDK-iOS/.gitattributes b/submodules/HockeySDK-iOS/.gitattributes deleted file mode 100644 index 854d90babf..0000000000 --- a/submodules/HockeySDK-iOS/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -*.pbxproj merge=union \ No newline at end of file diff --git a/submodules/HockeySDK-iOS/.gitignore b/submodules/HockeySDK-iOS/.gitignore deleted file mode 100644 index db0ab09450..0000000000 --- a/submodules/HockeySDK-iOS/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -fastlane/README.md -fastlane/report.xml -fastlane/test_output/* -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 -xcuserdata -*.xccheckout -*.xcscmblueprint -*.moved-aside -DerivedData -*.hmap -*.ipa -*.xcuserstate -.DS_Store -*.dSYM -*.dSYM.zip -*.ipa -*/xcuserdata/* -HockeySDK-iOS.xcodeproj/* diff --git a/submodules/HockeySDK-iOS/BUCK b/submodules/HockeySDK-iOS/BUCK deleted file mode 100644 index 582118aa4b..0000000000 --- a/submodules/HockeySDK-iOS/BUCK +++ /dev/null @@ -1,60 +0,0 @@ -load("//Config:buck_rule_macros.bzl", "static_library") - -prebuilt_apple_framework( - name = "CrashReporter", - framework = "Vendor/CrashReporter.framework", - preferred_linkage = "static", -) - -static_library( - name = "HockeySDK", - srcs = glob([ - "Classes/*.m", - "Classes/*.mm", - ]), - headers = glob([ - "Classes/*.h", - ]), - exported_headers = [ - "Classes/HockeySDKFeatureConfig.h", - "Classes/HockeySDKEnums.h", - "Classes/HockeySDKNullability.h", - "Classes/BITAlertAction.h", - - "Classes/BITHockeyManager.h", - - "Classes/BITHockeyAttachment.h", - - "Classes/BITHockeyBaseManager.h", - "Classes/BITCrashManager.h", - "Classes/BITCrashAttachment.h", - "Classes/BITCrashManagerDelegate.h", - "Classes/BITCrashDetails.h", - "Classes/BITCrashMetaData.h", - - "Classes/BITAuthenticator.h", - - "Classes/BITUpdateManager.h", - "Classes/BITUpdateManagerDelegate.h", - "Classes/BITUpdateViewController.h", - "Classes/BITHockeyBaseViewController.h", - "Classes/BITHockeyManagerDelegate.h", - ], - compiler_flags = [ - '-DBITHOCKEY_VERSION=@\"5.1.2\"', - '-DBITHOCKEY_C_VERSION="5.1.2"', - '-DBITHOCKEY_C_BUILD="108"', - "-DHOCKEYSDK_FEATURE_CRASH_REPORTER=1", - "-DHOCKEYSDK_FEATURE_UPDATES=1", - "-DHOCKEYSDK_FEATURE_FEEDBACK=0", - "-DHOCKEYSDK_FEATURE_AUTHENTICATOR=1", - "-DHOCKEYSDK_FEATURE_METRICS=0", - ], - deps = [ - ":CrashReporter", - ], - frameworks = [ - "$SDKROOT/System/Library/Frameworks/Foundation.framework", - "$SDKROOT/System/Library/Frameworks/UIKit.framework", - ], -) diff --git a/submodules/HockeySDK-iOS/Classes/BITActivityIndicatorButton.h b/submodules/HockeySDK-iOS/Classes/BITActivityIndicatorButton.h deleted file mode 100644 index 2c633222c9..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITActivityIndicatorButton.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import - -@interface BITActivityIndicatorButton : UIButton - -- (void)setShowsActivityIndicator:(BOOL)showsIndicator; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITActivityIndicatorButton.m b/submodules/HockeySDK-iOS/Classes/BITActivityIndicatorButton.m deleted file mode 100644 index 6a731bb434..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITActivityIndicatorButton.m +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_FEEDBACK - -#import "BITActivityIndicatorButton.h" - -@interface BITActivityIndicatorButton() - -@property (nonatomic, strong) UIActivityIndicatorView *indicator; -@property (nonatomic) BOOL indicatorVisible; - -@end - -@implementation BITActivityIndicatorButton - -- (void)setShowsActivityIndicator:(BOOL)showsIndicator { - if (self.indicatorVisible == showsIndicator){ - return; - } - - if (!self.indicator){ - self.indicator = [[UIActivityIndicatorView alloc] initWithFrame:self.bounds]; - [self addSubview:self.indicator]; - [self.indicator setColor:[UIColor blackColor]]; - } - - self.indicatorVisible = showsIndicator; - - if (showsIndicator){ - [self.indicator startAnimating]; - self.indicator.alpha = 1; - self.layer.borderWidth = 1; - self.layer.borderColor = [UIColor lightGrayColor].CGColor; - self.layer.cornerRadius = 5; - self.imageView.image = nil; - } else { - [self.indicator stopAnimating]; - self.layer.cornerRadius = 0; - self.indicator.alpha = 0; - self.layer.borderWidth = 0; - - } - -} - -- (void)layoutSubviews { - [super layoutSubviews]; - - [self.indicator setFrame:self.bounds]; - -} - - -@end - -#endif /* HOCKEYSDK_FEATURE_FEEDBACK */ diff --git a/submodules/HockeySDK-iOS/Classes/BITAlertAction.h b/submodules/HockeySDK-iOS/Classes/BITAlertAction.h deleted file mode 100644 index caf1277f5c..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITAlertAction.h +++ /dev/null @@ -1,9 +0,0 @@ -#import - -@interface BITAlertAction : UIAlertAction - -+ (UIAlertAction * _Nonnull)actionWithTitle:(nullable NSString *)title style:(UIAlertActionStyle)style handler:(void (^_Nullable)(UIAlertAction *_Nonnull))handler; - -- (void)invokeAction; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITAlertAction.m b/submodules/HockeySDK-iOS/Classes/BITAlertAction.m deleted file mode 100644 index d689a5a960..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITAlertAction.m +++ /dev/null @@ -1,23 +0,0 @@ -#import "BITAlertAction.h" - -@interface BITAlertAction () - -@property (nonatomic, copy) void (^storedHandler)(UIAlertAction * _Nonnull); - -@end - -@implementation BITAlertAction - -+ (UIAlertAction *)actionWithTitle:(nullable NSString *)title style:(UIAlertActionStyle)style handler:(void (^)(UIAlertAction *_Nonnull))handler { - BITAlertAction *action = [super actionWithTitle:title style:style handler:handler]; - action.storedHandler = handler; - return action; -} - -- (void)invokeAction { - if (self.storedHandler) { - self.storedHandler(self); - } -} - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITAppStoreHeader.h b/submodules/HockeySDK-iOS/Classes/BITAppStoreHeader.h deleted file mode 100644 index c0b5e8f955..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITAppStoreHeader.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Author: Andreas Linde - * Peter Steinberger - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * Copyright (c) 2011-2012 Peter Steinberger. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - - -#import - -@interface BITAppStoreHeader : UIView - -@property (nonatomic, copy) NSString *headerText; -@property (nonatomic, copy) NSString *subHeaderText; -@property (nonatomic, strong) UIImage *iconImage; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITAppStoreHeader.m b/submodules/HockeySDK-iOS/Classes/BITAppStoreHeader.m deleted file mode 100644 index 54e72abf76..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITAppStoreHeader.m +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Author: Andreas Linde - * Peter Steinberger - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * Copyright (c) 2011-2012 Peter Steinberger. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_UPDATES - -#import "BITAppStoreHeader.h" -#import "BITHockeyHelper.h" -#import "HockeySDKPrivate.h" - -#define kDarkGrayColor BIT_RGBCOLOR(186, 186, 186) -#define kWhiteBackgroundColorDefault BIT_RGBCOLOR(245, 245, 245) -#define kWhiteBackgroundColorOS7 BIT_RGBCOLOR(255, 255, 255) -#define kImageHeight 72 -#define kImageBorderRadiusiOS7 16.5 -#define kImageLeftMargin 14 -#define kImageTopMargin 12 -#define kTextRow kImageTopMargin*2 + kImageHeight - -@interface BITAppStoreHeader () - -@property (nonatomic, strong) UILabel *headerLabelView; -@property (nonatomic, strong) UILabel *middleLabelView; - -@end - -@implementation BITAppStoreHeader - - -#pragma mark - NSObject - -- (instancetype)initWithFrame:(CGRect)frame { - if ((self = [super initWithFrame:frame])) { - self.autoresizingMask = UIViewAutoresizingFlexibleWidth; - self.backgroundColor = kWhiteBackgroundColorDefault; - } - return self; -} - - -#pragma mark - UIView - -- (void)drawRect:(CGRect)rect { - CGRect bounds = self.bounds; - - // draw the line - CGContextRef ctx = UIGraphicsGetCurrentContext(); - CGContextSetLineWidth(ctx, 1.0); - CGContextSetStrokeColorWithColor(ctx, kDarkGrayColor.CGColor); - CGContextMoveToPoint(ctx, 0, CGRectGetMaxY(bounds)); - CGContextAddLineToPoint( ctx, CGRectGetMaxX(bounds), CGRectGetMaxY(bounds)); - CGContextStrokePath(ctx); - - // icon - [self.iconImage drawAtPoint:CGPointMake(kImageLeftMargin, kImageTopMargin)]; - - [super drawRect:rect]; -} - - -- (void)layoutSubviews { - self.backgroundColor = kWhiteBackgroundColorOS7; - - [super layoutSubviews]; - - CGFloat globalWidth = self.frame.size.width; - - // draw header name - UIColor *mainTextColor = BIT_RGBCOLOR(61, 61, 61); - UIColor *secondaryTextColor = BIT_RGBCOLOR(100, 100, 100); - UIFont *mainFont = [UIFont boldSystemFontOfSize:15]; - UIFont *secondaryFont = [UIFont systemFontOfSize:10]; - - if (!self.headerLabelView) self.headerLabelView = [[UILabel alloc] init]; - [self.headerLabelView setFont:mainFont]; - [self.headerLabelView setFrame:CGRectMake(kTextRow, kImageTopMargin, globalWidth-kTextRow, 20)]; - [self.headerLabelView setTextColor:mainTextColor]; - [self.headerLabelView setBackgroundColor:[UIColor clearColor]]; - [self.headerLabelView setText:self.headerText]; - [self addSubview:self.headerLabelView]; - - // middle - if (!self.middleLabelView) self.middleLabelView = [[UILabel alloc] init]; - [self.middleLabelView setFont:secondaryFont]; - [self.middleLabelView setFrame:CGRectMake(kTextRow, kImageTopMargin + 17, globalWidth-kTextRow, 20)]; - [self.middleLabelView setTextColor:secondaryTextColor]; - [self.middleLabelView setBackgroundColor:[UIColor clearColor]]; - [self.middleLabelView setText:self.subHeaderText]; - [self addSubview:self.middleLabelView]; -} - - -#pragma mark - Properties - -- (void)setHeaderText:(NSString *)anHeaderText { - if (_headerText != anHeaderText) { - _headerText = [anHeaderText copy]; - [self setNeedsDisplay]; - } -} - -- (void)setSubHeaderText:(NSString *)aSubHeaderText { - if (_subHeaderText != aSubHeaderText) { - _subHeaderText = [aSubHeaderText copy]; - [self setNeedsDisplay]; - } -} - -- (void)setIconImage:(UIImage *)anIconImage { - if (_iconImage != anIconImage) { - - // scale, make borders and reflection - _iconImage = bit_imageToFitSize(anIconImage, CGSizeMake(kImageHeight, kImageHeight), YES); - _iconImage = bit_roundedCornerImage(_iconImage, kImageBorderRadiusiOS7, 0.0); - - [self setNeedsDisplay]; - } -} - -@end - -#endif /* HOCKEYSDK_FEATURE_UPDATES */ diff --git a/submodules/HockeySDK-iOS/Classes/BITAppVersionMetaInfo.h b/submodules/HockeySDK-iOS/Classes/BITAppVersionMetaInfo.h deleted file mode 100644 index 2143e02a72..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITAppVersionMetaInfo.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Author: Peter Steinberger - * Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * Copyright (c) 2011 Andreas Linde, Peter Steinberger. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import - -@interface BITAppVersionMetaInfo : NSObject { -} -@property (nonatomic, copy) NSString *name; -@property (nonatomic, copy) NSString *version; -@property (nonatomic, copy) NSString *shortVersion; -@property (nonatomic, copy) NSString *minOSVersion; -@property (nonatomic, copy) NSString *notes; -@property (nonatomic, copy) NSDate *date; -@property (nonatomic, copy) NSNumber *size; -@property (nonatomic, copy) NSNumber *mandatory; -@property (nonatomic, copy) NSNumber *versionID; -@property (nonatomic, copy) NSDictionary *uuids; - -- (NSString *)nameAndVersionString; -- (NSString *)versionString; -- (NSString *)dateString; -- (NSString *)sizeInMB; -- (NSString *)notesOrEmptyString; -- (void)setDateWithTimestamp:(NSTimeInterval)timestamp; -- (BOOL)isValid; -- (BOOL)hasUUID:(NSString *)uuid; -- (BOOL)isEqualToAppVersionMetaInfo:(BITAppVersionMetaInfo *)anAppVersionMetaInfo; - -+ (BITAppVersionMetaInfo *)appVersionMetaInfoFromDict:(NSDictionary *)dict; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITAppVersionMetaInfo.m b/submodules/HockeySDK-iOS/Classes/BITAppVersionMetaInfo.m deleted file mode 100644 index 9320ed5092..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITAppVersionMetaInfo.m +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Author: Peter Steinberger - * Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * Copyright (c) 2011 Andreas Linde, Peter Steinberger. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "BITAppVersionMetaInfo.h" -#import "HockeySDKPrivate.h" - - -@implementation BITAppVersionMetaInfo - - -#pragma mark - Static - -+ (BITAppVersionMetaInfo *)appVersionMetaInfoFromDict:(NSDictionary *)dict { - BITAppVersionMetaInfo *appVersionMetaInfo = [[[self class] alloc] init]; - - if ([dict isKindOfClass:[NSDictionary class]]) { - appVersionMetaInfo.name = [dict objectForKey:@"title"]; - appVersionMetaInfo.version = [dict objectForKey:@"version"]; - appVersionMetaInfo.shortVersion = [dict objectForKey:@"shortversion"]; - appVersionMetaInfo.minOSVersion = [dict objectForKey:@"minimum_os_version"]; - [appVersionMetaInfo setDateWithTimestamp:[[dict objectForKey:@"timestamp"] doubleValue]]; - appVersionMetaInfo.size = [dict objectForKey:@"appsize"]; - appVersionMetaInfo.notes = [dict objectForKey:@"notes"]; - appVersionMetaInfo.mandatory = [dict objectForKey:@"mandatory"]; - appVersionMetaInfo.versionID = [dict objectForKey:@"id"]; - appVersionMetaInfo.uuids = [dict objectForKey:@"uuids"]; - } - - return appVersionMetaInfo; -} - - -#pragma mark - NSObject - -- (BOOL)isEqual:(id)other { - if (other == self) - return YES; - if (!other || ![other isKindOfClass:[self class]]) - return NO; - return [self isEqualToAppVersionMetaInfo:other]; -} - -- (BOOL)isEqualComparingString:(NSString *)stringA withString:(NSString *)stringB { - if ([stringA isKindOfClass:[NSString class]] && [stringB isKindOfClass:[NSString class]]) { - return [stringA isEqualToString:stringB]; - } - - return NO; -} - -- (BOOL)isEqualComparingNumber:(NSNumber *)numberA withNumber:(NSNumber *)numberB { - if ([numberA isKindOfClass:[NSNumber class]] && [numberB isKindOfClass:[NSNumber class]]) { - return [numberA isEqualToNumber:numberB]; - } - - return NO; -} - -- (BOOL)isEqualComparingDate:(NSDate *)dateA withDate:(NSDate *)dateB { - if ([dateA isKindOfClass:[NSDate class]] && [dateB isKindOfClass:[NSDate class]]) { - return [dateA isEqualToDate:dateB]; - } - - return NO; -} - -- (BOOL)isEqualComparingDictionary:(NSDictionary *)dictA withDate:(NSDictionary *)dictB { - if ([dictA isKindOfClass:[NSDictionary class]] && [dictB isKindOfClass:[NSDictionary class]]) { - return [dictA isEqualToDictionary:dictB]; - } - - return NO; -} - -- (BOOL)isEqualToAppVersionMetaInfo:(BITAppVersionMetaInfo *)anAppVersionMetaInfo { - if (self == anAppVersionMetaInfo) - return YES; - if (![self isEqualComparingString:self.name withString:anAppVersionMetaInfo.name]) - return NO; - if (![self isEqualComparingString:self.version withString:anAppVersionMetaInfo.version]) - return NO; - if (![self isEqualComparingString:self.shortVersion withString:anAppVersionMetaInfo.shortVersion]) - return NO; - if (![self isEqualComparingString:self.minOSVersion withString:anAppVersionMetaInfo.minOSVersion]) - return NO; - if (![self isEqualComparingString:self.notes withString:anAppVersionMetaInfo.notes]) - return NO; - if (![self isEqualComparingDate:self.date withDate:anAppVersionMetaInfo.date]) - return NO; - if (![self isEqualComparingNumber:self.size withNumber:anAppVersionMetaInfo.size]) - return NO; - if (![self isEqualComparingNumber:self.mandatory withNumber:anAppVersionMetaInfo.mandatory]) - return NO; - if (![self isEqualComparingDictionary:self.uuids withDate:anAppVersionMetaInfo.uuids]) - return NO; - return YES; -} - - -#pragma mark - NSCoder - -- (void)encodeWithCoder:(NSCoder *)encoder { - [encoder encodeObject:self.name forKey:@"name"]; - [encoder encodeObject:self.version forKey:@"version"]; - [encoder encodeObject:self.shortVersion forKey:@"shortVersion"]; - [encoder encodeObject:self.minOSVersion forKey:@"minOSVersion"]; - [encoder encodeObject:self.notes forKey:@"notes"]; - [encoder encodeObject:self.date forKey:@"date"]; - [encoder encodeObject:self.size forKey:@"size"]; - [encoder encodeObject:self.mandatory forKey:@"mandatory"]; - [encoder encodeObject:self.versionID forKey:@"versionID"]; - [encoder encodeObject:self.uuids forKey:@"uuids"]; -} - -- (instancetype)initWithCoder:(NSCoder *)decoder { - if ((self = [super init])) { - self.name = [decoder decodeObjectForKey:@"name"]; - self.version = [decoder decodeObjectForKey:@"version"]; - self.shortVersion = [decoder decodeObjectForKey:@"shortVersion"]; - self.minOSVersion = [decoder decodeObjectForKey:@"minOSVersion"]; - self.notes = [decoder decodeObjectForKey:@"notes"]; - self.date = [decoder decodeObjectForKey:@"date"]; - self.size = [decoder decodeObjectForKey:@"size"]; - self.mandatory = [decoder decodeObjectForKey:@"mandatory"]; - self.versionID = [decoder decodeObjectForKey:@"versionID"]; - self.uuids = [decoder decodeObjectForKey:@"uuids"]; - } - return self; -} - - -#pragma mark - Properties - -- (NSString *)nameAndVersionString { - NSString *appNameAndVersion = [NSString stringWithFormat:@"%@ %@", self.name, [self versionString]]; - return appNameAndVersion; -} - -- (NSString *)versionString { - NSString *shortString = ([self.shortVersion respondsToSelector:@selector(length)] && [self.shortVersion length]) ? [NSString stringWithFormat:@"%@", self.shortVersion] : @""; - NSString *versionString = [shortString length] ? [NSString stringWithFormat:@" (%@)", self.version] : self.version; - return [NSString stringWithFormat:@"%@ %@%@", BITHockeyLocalizedString(@"Version"), shortString, versionString]; -} - -- (NSString *)dateString { - NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; - [formatter setDateStyle:NSDateFormatterMediumStyle]; - - return [formatter stringFromDate:self.date]; -} - -- (NSString *)sizeInMB { - if ([self.size isKindOfClass: [NSNumber class]] && [self.size doubleValue] > 0) { - double appSizeInMB = [self.size doubleValue]/(1024*1024); - NSString *appSizeString = [NSString stringWithFormat:@"%.1f MB", appSizeInMB]; - return appSizeString; - } - - return @"0 MB"; -} - -- (void)setDateWithTimestamp:(NSTimeInterval)timestamp { - if (timestamp != 0) { - NSDate *appDate = [NSDate dateWithTimeIntervalSince1970:timestamp]; - self.date = appDate; - } else { - self.date = nil; - } -} - -- (NSString *)notesOrEmptyString { - if (self.notes) { - return self.notes; - }else { - return [NSString string]; - } -} - - -// A valid app needs at least following properties: name, version, date -- (BOOL)isValid { - BOOL valid = [self.name length] && [self.version length] && self.date; - return valid; -} - -- (BOOL)hasUUID:(NSString *)uuid { - if (!uuid) return NO; - if (!self.uuids) return NO; - - __block BOOL hasUUID = NO; - - [self.uuids enumerateKeysAndObjectsUsingBlock:^(id __unused key, id obj, BOOL *stop){ - if (obj && [uuid compare:obj] == NSOrderedSame) { - hasUUID = YES; - *stop = YES; - } - }]; - - return hasUUID; -} -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITApplication.h b/submodules/HockeySDK-iOS/Classes/BITApplication.h deleted file mode 100755 index 6a03147e2d..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITApplication.h +++ /dev/null @@ -1,12 +0,0 @@ -#import "BITTelemetryObject.h" - -@interface BITApplication : BITTelemetryObject - -@property (nonatomic, copy) NSString *version; -@property (nonatomic, copy) NSString *build; -@property (nonatomic, copy) NSString *typeId; - -- (instancetype)initWithCoder:(NSCoder *)coder; -- (void)encodeWithCoder:(NSCoder *)coder; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITApplication.m b/submodules/HockeySDK-iOS/Classes/BITApplication.m deleted file mode 100755 index e3104f3616..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITApplication.m +++ /dev/null @@ -1,44 +0,0 @@ -#import "BITApplication.h" - -/// Data contract class for type Application. -@implementation BITApplication - -/// -/// Adds all members of this class to a dictionary -/// -- (NSDictionary *)serializeToDictionary { - NSMutableDictionary *dict = [super serializeToDictionary].mutableCopy; - if (self.version != nil) { - [dict setObject:self.version forKey:@"ai.application.ver"]; - } - if (self.build != nil) { - [dict setObject:self.build forKey:@"ai.application.build"]; - } - if (self.typeId != nil) { - [dict setObject:self.typeId forKey:@"ai.application.typeId"]; - } - return dict; -} - -#pragma mark - NSCoding - -- (instancetype)initWithCoder:(NSCoder *)coder { - self = [super initWithCoder:coder]; - if (self) { - _version = [coder decodeObjectForKey:@"self.version"]; - _build = [coder decodeObjectForKey:@"self.build"]; - _typeId = [coder decodeObjectForKey:@"self.typeId"]; - } - - return self; -} - -- (void)encodeWithCoder:(NSCoder *)coder { - [super encodeWithCoder:coder]; - [coder encodeObject:self.version forKey:@"self.version"]; - [coder encodeObject:self.build forKey:@"self.build"]; - [coder encodeObject:self.typeId forKey:@"self.typeId"]; -} - - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITArrowImageAnnotation.h b/submodules/HockeySDK-iOS/Classes/BITArrowImageAnnotation.h deleted file mode 100644 index feeca7613b..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITArrowImageAnnotation.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Author: Moritz Haarmann - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "BITImageAnnotation.h" - -@interface BITArrowImageAnnotation : BITImageAnnotation - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITArrowImageAnnotation.m b/submodules/HockeySDK-iOS/Classes/BITArrowImageAnnotation.m deleted file mode 100644 index d74a76cf10..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITArrowImageAnnotation.m +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Author: Moritz Haarmann - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_FEEDBACK - -#import "BITArrowImageAnnotation.h" -#import - -#define kArrowPointCount 7 - - -@interface BITArrowImageAnnotation() - -@property (nonatomic, strong) CAShapeLayer *shapeLayer; -@property (nonatomic, strong) CAShapeLayer *strokeLayer; - -@end - -@implementation BITArrowImageAnnotation - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - self.shapeLayer = [CAShapeLayer layer]; - self.shapeLayer.strokeColor = [UIColor whiteColor].CGColor; - self.shapeLayer.lineWidth = 5; - self.shapeLayer.fillColor = [UIColor redColor].CGColor; - - self.strokeLayer = [CAShapeLayer layer]; - self.strokeLayer.strokeColor = [UIColor redColor].CGColor; - self.strokeLayer.lineWidth = 10; - self.strokeLayer.fillColor = [UIColor clearColor].CGColor; - [self.layer addSublayer:self.strokeLayer]; - - [self.layer addSublayer:self.shapeLayer]; - - - } - return self; -} - -- (void)buildShape { - CGFloat baseWidth = MAX(self.frame.size.width, self.frame.size.height); - CGFloat topHeight = MAX(baseWidth / 3,10); - - - CGFloat lineWidth = MAX(baseWidth / 10,3); - CGFloat startX, startY, endX, endY; - - CGRect boundRect = CGRectInset(self.bounds, 0, 0); - CGFloat arrowLength= sqrt(pow(CGRectGetWidth(boundRect), (CGFloat)2) + pow(CGRectGetHeight(boundRect), (CGFloat)2)); - if (0 < arrowLength && arrowLength < 30){ - - CGFloat factor = 30 / arrowLength; - - boundRect = CGRectApplyAffineTransform(boundRect, CGAffineTransformMakeScale(factor,factor)); - } - - if ( self.movedDelta.width < 0){ - startX = CGRectGetMinX(boundRect); - endX = CGRectGetMaxX(boundRect); - } else { - startX = CGRectGetMaxX(boundRect); - endX = CGRectGetMinX(boundRect); - - } - - if ( self.movedDelta.height < 0){ - startY = CGRectGetMinY(boundRect); - endY = CGRectGetMaxY(boundRect); - } else { - startY = CGRectGetMaxY(boundRect); - endY = CGRectGetMinY(boundRect); - - } - - - if (fabs(CGRectGetWidth(boundRect)) < 30 || fabs(CGRectGetHeight(boundRect)) < 30){ - CGFloat smallerOne = MIN(fabs(CGRectGetHeight(boundRect)), fabs(CGRectGetWidth(boundRect))); - - CGFloat factor = smallerOne / 30; - - CGRectApplyAffineTransform(boundRect, CGAffineTransformMakeScale(factor,factor)); - } - - UIBezierPath *path = [self bezierPathWithArrowFromPoint:CGPointMake(endX, endY) toPoint:CGPointMake(startX, startY) tailWidth:lineWidth headWidth:topHeight headLength:topHeight]; - - self.shapeLayer.path = path.CGPath; - self.strokeLayer.path = path.CGPath; - [CATransaction begin]; - [CATransaction setAnimationDuration:0]; - self.strokeLayer.lineWidth = lineWidth / (CGFloat)1.5; - self.shapeLayer.lineWidth = lineWidth / 3; - - [CATransaction commit]; - -} - - -- (UIBezierPath *)bezierPathWithArrowFromPoint:(CGPoint)startPoint - toPoint:(CGPoint)endPoint - tailWidth:(CGFloat)tailWidth - headWidth:(CGFloat)headWidth - headLength:(CGFloat)headLength { - CGFloat length = hypot(endPoint.x - startPoint.x, endPoint.y - startPoint.y); - - CGPoint points[kArrowPointCount]; - [self getAxisAlignedArrowPoints:points - forLength:length - tailWidth:tailWidth - headWidth:headWidth - headLength:headLength]; - - CGAffineTransform transform = [self transformForStartPoint:startPoint - endPoint:endPoint - length:length]; - - CGMutablePathRef cgPath = CGPathCreateMutable(); - CGPathAddLines(cgPath, &transform, points, sizeof points / sizeof *points); - CGPathCloseSubpath(cgPath); - - UIBezierPath *uiPath = [UIBezierPath bezierPathWithCGPath:cgPath]; - CGPathRelease(cgPath); - return uiPath; -} - -- (void)getAxisAlignedArrowPoints:(CGPoint[kArrowPointCount])points - forLength:(CGFloat)length - tailWidth:(CGFloat)tailWidth - headWidth:(CGFloat)headWidth - headLength:(CGFloat)headLength { - CGFloat tailLength = length - headLength; - points[0] = CGPointMake(0, tailWidth / 2); - points[1] = CGPointMake(tailLength, tailWidth / 2); - points[2] = CGPointMake(tailLength, headWidth / 2); - points[3] = CGPointMake(length, 0); - points[4] = CGPointMake(tailLength, -headWidth / 2); - points[5] = CGPointMake(tailLength, -tailWidth / 2); - points[6] = CGPointMake(0, -tailWidth / 2); -} - -+ (CGAffineTransform)dqd_transformForStartPoint:(CGPoint)startPoint - endPoint:(CGPoint)endPoint - length:(CGFloat)length { - CGFloat cosine = (endPoint.x - startPoint.x) / length; - CGFloat sine = (endPoint.y - startPoint.y) / length; - return (CGAffineTransform){ cosine, sine, -sine, cosine, startPoint.x, startPoint.y }; -} - -- (CGAffineTransform)transformForStartPoint:(CGPoint)startPoint - endPoint:(CGPoint)endPoint - length:(CGFloat)length { - if (CGPointEqualToPoint(startPoint, CGPointZero) && (length == 0)) { - return CGAffineTransformIdentity; - } - - CGFloat cosine = (endPoint.x - startPoint.x) / length; - CGFloat sine = (endPoint.y - startPoint.y) / length; - return (CGAffineTransform){ cosine, sine, -sine, cosine, startPoint.x, startPoint.y }; -} - -#pragma mark - UIView - -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *) __unused event { - - CGPathRef strokePath = CGPathCreateCopyByStrokingPath(self.shapeLayer.path, NULL, fmax(90.0f, self.shapeLayer.lineWidth), kCGLineCapRound,kCGLineJoinMiter,0); - - BOOL containsPoint = CGPathContainsPoint(strokePath, NULL, point, NO); - - CGPathRelease(strokePath); - - if (containsPoint){ - return self; - } else { - return nil; - } - -} - -- (void)layoutSubviews{ - [super layoutSubviews]; - - [self buildShape]; -} - -@end - -#endif /* HOCKEYSDK_FEATURE_FEEDBACK */ diff --git a/submodules/HockeySDK-iOS/Classes/BITAttributedLabel.h b/submodules/HockeySDK-iOS/Classes/BITAttributedLabel.h deleted file mode 100644 index 720aaf37ef..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITAttributedLabel.h +++ /dev/null @@ -1,681 +0,0 @@ -// TTTAttributedLabel.h -// -// Copyright (c) 2011 Mattt Thompson (http://mattt.me) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -#import -#import - -//! Project version number for BITAttributedLabel. -FOUNDATION_EXPORT double BITAttributedLabelVersionNumber; - -//! Project version string for BITAttributedLabel. -FOUNDATION_EXPORT const unsigned char BITAttributedLabelVersionString[]; - -@class BITAttributedLabelLink; - -/** - Vertical alignment for text in a label whose bounds are larger than its text bounds - */ -typedef NS_ENUM(NSInteger, BITAttributedLabelVerticalAlignment) { - BITAttributedLabelVerticalAlignmentCenter = 0, - BITAttributedLabelVerticalAlignmentTop = 1, - BITAttributedLabelVerticalAlignmentBottom = 2, -}; - -/** - Determines whether the text to which this attribute applies has a strikeout drawn through itself. - */ -extern NSString * const kBITStrikeOutAttributeName; - -/** - The background fill color. Value must be a `CGColorRef`. Default value is `nil` (no fill). - */ -extern NSString * const kBITBackgroundFillColorAttributeName; - -/** - The padding for the background fill. Value must be a `UIEdgeInsets`. Default value is `UIEdgeInsetsZero` (no padding). - */ -extern NSString * const kBITBackgroundFillPaddingAttributeName; - -/** - The background stroke color. Value must be a `CGColorRef`. Default value is `nil` (no stroke). - */ -extern NSString * const kBITBackgroundStrokeColorAttributeName; - -/** - The background stroke line width. Value must be an `NSNumber`. Default value is `1.0f`. - */ -extern NSString * const kBITBackgroundLineWidthAttributeName; - -/** - The background corner radius. Value must be an `NSNumber`. Default value is `5.0f`. - */ -extern NSString * const kBITBackgroundCornerRadiusAttributeName; - -@protocol BITAttributedLabelDelegate; - -// Override UILabel @property to accept both NSString and NSAttributedString -@protocol BITAttributedLabel -@property (nonatomic, copy) IBInspectable id text; -@end - -IB_DESIGNABLE - -/** - `BITAttributedLabel` is a drop-in replacement for `UILabel` that supports `NSAttributedString`, as well as automatically-detected and manually-added links to URLs, addresses, phone numbers, and dates. - - ## Differences Between `BITAttributedLabel` and `UILabel` - - For the most part, `BITAttributedLabel` behaves just like `UILabel`. The following are notable exceptions, in which `BITAttributedLabel` may act differently: - - - `text` - This property now takes an `id` type argument, which can either be a kind of `NSString` or `NSAttributedString` (mutable or immutable in both cases) - - `attributedText` - Do not set this property directly. Instead, pass an `NSAttributedString` to `text`. - - `lineBreakMode` - This property displays only the first line when the value is `UILineBreakModeHeadTruncation`, `UILineBreakModeTailTruncation`, or `UILineBreakModeMiddleTruncation` - - `adjustsFontsizeToFitWidth` - Supported in iOS 5 and greater, this property is effective for any value of `numberOfLines` greater than zero. In iOS 4, setting `numberOfLines` to a value greater than 1 with `adjustsFontSizeToFitWidth` set to `YES` may cause `sizeToFit` to execute indefinitely. - - `baselineAdjustment` - This property has no affect. - - `textAlignment` - This property does not support justified alignment. - - `NSTextAttachment` - This string attribute is not supported. - - Any properties affecting text or paragraph styling, such as `firstLineIndent` will only apply when text is set with an `NSString`. If the text is set with an `NSAttributedString`, these properties will not apply. - - ### NSCoding - - `BITAttributedLabel`, like `UILabel`, conforms to `NSCoding`. However, if the build target is set to less than iOS 6.0, `linkAttributes` and `activeLinkAttributes` will not be encoded or decoded. This is due to an runtime exception thrown when attempting to copy non-object CoreText values in dictionaries. - - @warning Any properties changed on the label after setting the text will not be reflected until a subsequent call to `setText:` or `setText:afterInheritingLabelAttributesAndConfiguringWithBlock:`. This is to say, order of operations matters in this case. For example, if the label text color is originally black when the text is set, changing the text color to red will have no effect on the display of the label until the text is set once again. - - @bug Setting `attributedText` directly is not recommended, as it may cause a crash when attempting to access any links previously set. Instead, call `setText:`, passing an `NSAttributedString`. - */ -@interface BITAttributedLabel : UILabel - -/** - * The designated initializers are @c initWithFrame: and @c initWithCoder:. - * init will not properly initialize many required properties and other configuration. - */ -- (instancetype) init NS_UNAVAILABLE; - -///----------------------------- -/// @name Accessing the Delegate -///----------------------------- - -/** - The receiver's delegate. - - @discussion A `BITAttributedLabel` delegate responds to messages sent by tapping on links in the label. You can use the delegate to respond to links referencing a URL, address, phone number, date, or date with a specified time zone and duration. - */ -@property (nonatomic, unsafe_unretained) IBOutlet id delegate; - -///-------------------------------------------- -/// @name Detecting, Accessing, & Styling Links -///-------------------------------------------- - -/** - A bitmask of `NSTextCheckingType` which are used to automatically detect links in the label text. - - @warning You must specify `enabledTextCheckingTypes` before setting the `text`, with either `setText:` or `setText:afterInheritingLabelAttributesAndConfiguringWithBlock:`. - */ -@property (nonatomic, assign) NSTextCheckingTypes enabledTextCheckingTypes; - -/** - An array of `NSTextCheckingResult` objects for links detected or manually added to the label text. - */ -@property (readonly, nonatomic, strong) NSArray *links; - -/** - A dictionary containing the default `NSAttributedString` attributes to be applied to links detected or manually added to the label text. The default link style is blue and underlined. - - @warning You must specify `linkAttributes` before setting autodecting or manually-adding links for these attributes to be applied. - */ -@property (nonatomic, strong) NSDictionary *linkAttributes; - -/** - A dictionary containing the default `NSAttributedString` attributes to be applied to links when they are in the active state. If `nil` or an empty `NSDictionary`, active links will not be styled. The default active link style is red and underlined. - */ -@property (nonatomic, strong) NSDictionary *activeLinkAttributes; - -/** - A dictionary containing the default `NSAttributedString` attributes to be applied to links when they are in the inactive state, which is triggered by a change in `tintColor` in iOS 7 and later. If `nil` or an empty `NSDictionary`, inactive links will not be styled. The default inactive link style is gray and unadorned. - */ -@property (nonatomic, strong) NSDictionary *inactiveLinkAttributes; - -/** - The edge inset for the background of a link. The default value is `{0, -1, 0, -1}`. - */ -@property (nonatomic, assign) UIEdgeInsets linkBackgroundEdgeInset; - -/** - Indicates if links will be detected within an extended area around the touch - to emulate the link detection behaviour of UIWebView. - Default value is NO. Enabling this may adversely impact performance. - */ -@property (nonatomic, assign) BOOL extendsLinkTouchArea; - -///--------------------------------------- -/// @name Acccessing Text Style Attributes -///--------------------------------------- - -/** - The shadow blur radius for the label. A value of 0 indicates no blur, while larger values produce correspondingly larger blurring. This value must not be negative. The default value is 0. - */ -@property (nonatomic, assign) IBInspectable CGFloat shadowRadius; - -/** - The shadow blur radius for the label when the label's `highlighted` property is `YES`. A value of 0 indicates no blur, while larger values produce correspondingly larger blurring. This value must not be negative. The default value is 0. - */ -@property (nonatomic, assign) IBInspectable CGFloat highlightedShadowRadius; -/** - The shadow offset for the label when the label's `highlighted` property is `YES`. A size of {0, 0} indicates no offset, with positive values extending down and to the right. The default size is {0, 0}. - */ -@property (nonatomic, assign) IBInspectable CGSize highlightedShadowOffset; -/** - The shadow color for the label when the label's `highlighted` property is `YES`. The default value is `nil` (no shadow color). - */ -@property (nonatomic, strong) IBInspectable UIColor *highlightedShadowColor; - -/** - The amount to kern the next character. Default is standard kerning. If this attribute is set to 0.0, no kerning is done at all. - */ -@property (nonatomic, assign) IBInspectable CGFloat kern; - -///-------------------------------------------- -/// @name Acccessing Paragraph Style Attributes -///-------------------------------------------- - -/** - The distance, in points, from the leading margin of a frame to the beginning of the - paragraph's first line. This value is always nonnegative, and is 0.0 by default. - This applies to the full text, rather than any specific paragraph metrics. - */ -@property (nonatomic, assign) IBInspectable CGFloat firstLineIndent; - -/** - The space in points added between lines within the paragraph. This value is always nonnegative and is 0.0 by default. - */ -@property (nonatomic, assign) IBInspectable CGFloat lineSpacing; - -/** - The minimum line height within the paragraph. If the value is 0.0, the minimum line height is set to the line height of the `font`. 0.0 by default. - */ -@property (nonatomic, assign) IBInspectable CGFloat minimumLineHeight; - -/** - The maximum line height within the paragraph. If the value is 0.0, the maximum line height is set to the line height of the `font`. 0.0 by default. - */ -@property (nonatomic, assign) IBInspectable CGFloat maximumLineHeight; - -/** - The line height multiple. This value is 1.0 by default. - */ -@property (nonatomic, assign) IBInspectable CGFloat lineHeightMultiple; - -/** - The distance, in points, from the margin to the text container. This value is `UIEdgeInsetsZero` by default. - sizeThatFits: will have its returned size increased by these margins. - drawTextInRect: will inset all drawn text by these margins. - */ -@property (nonatomic, assign) IBInspectable UIEdgeInsets textInsets; - -/** - The vertical text alignment for the label, for when the frame size is greater than the text rect size. The vertical alignment is `BITAttributedLabelVerticalAlignmentCenter` by default. - */ -@property (nonatomic, assign) BITAttributedLabelVerticalAlignment verticalAlignment; - -///-------------------------------------------- -/// @name Accessing Truncation Token Appearance -///-------------------------------------------- - -/** - The attributed string to apply to the truncation token at the end of a truncated line. - */ -@property (nonatomic, strong) IBInspectable NSAttributedString *attributedTruncationToken; - -///-------------------------- -/// @name Long press gestures -///-------------------------- - -/** - * The long-press gesture recognizer used internally by the label. - */ -@property (nonatomic, strong, readonly) UILongPressGestureRecognizer *longPressGestureRecognizer; - -///-------------------------------------------- -/// @name Calculating Size of Attributed String -///-------------------------------------------- - -/** - Calculate and return the size that best fits an attributed string, given the specified constraints on size and number of lines. - - @param attributedString The attributed string. - @param size The maximum dimensions used to calculate size. - @param numberOfLines The maximum number of lines in the text to draw, if the constraining size cannot accomodate the full attributed string. - - @return The size that fits the attributed string within the specified constraints. - */ -+ (CGSize)sizeThatFitsAttributedString:(NSAttributedString *)attributedString - withConstraints:(CGSize)size - limitedToNumberOfLines:(NSUInteger)numberOfLines; - -///---------------------------------- -/// @name Setting the Text Attributes -///---------------------------------- - -/** - Sets the text displayed by the label. - - @param text An `NSString` or `NSAttributedString` object to be displayed by the label. If the specified text is an `NSString`, the label will display the text like a `UILabel`, inheriting the text styles of the label. If the specified text is an `NSAttributedString`, the label text styles will be overridden by the styles specified in the attributed string. - - @discussion This method overrides `UILabel -setText:` to accept both `NSString` and `NSAttributedString` objects. This string is `nil` by default. - */ -- (void)setText:(id)text; - -/** - Sets the text displayed by the label, after configuring an attributed string containing the text attributes inherited from the label in a block. - - @param text An `NSString` or `NSAttributedString` object to be displayed by the label. - @param block A block object that returns an `NSMutableAttributedString` object and takes a single argument, which is an `NSMutableAttributedString` object with the text from the first parameter, and the text attributes inherited from the label text styles. For example, if you specified the `font` of the label to be `[UIFont boldSystemFontOfSize:14]` and `textColor` to be `[UIColor redColor]`, the `NSAttributedString` argument of the block would be contain the `NSAttributedString` attribute equivalents of those properties. In this block, you can set further attributes on particular ranges. - - @discussion This string is `nil` by default. - */ -- (void)setText:(id)text -afterInheritingLabelAttributesAndConfiguringWithBlock:(NSMutableAttributedString *(^)(NSMutableAttributedString *mutableAttributedString))block; - -///------------------------------------ -/// @name Accessing the Text Attributes -///------------------------------------ - -/** - A copy of the label's current attributedText. This returns `nil` if an attributed string has never been set on the label. - - @warning Do not set this property directly. Instead, set @c text to an @c NSAttributedString. - */ -@property (readwrite, nonatomic, copy) NSAttributedString *attributedText; - -///------------------- -/// @name Adding Links -///------------------- - -/** - Adds a link. You can customize an individual link's appearance and accessibility value by creating your own @c BITAttributedLabelLink and passing it to this method. The other methods for adding links will use the label's default attributes. - - @warning Modifying the link's attribute dictionaries must be done before calling this method. - - @param link A @c BITAttributedLabelLink object. - */ -- (void)addLink:(BITAttributedLabelLink *)link; - -/** - Adds a link to an @c NSTextCheckingResult. - - @param result An @c NSTextCheckingResult representing the link's location and type. - - @return The newly added link object. - */ -- (BITAttributedLabelLink *)addLinkWithTextCheckingResult:(NSTextCheckingResult *)result; - -/** - Adds a link to an @c NSTextCheckingResult. - - @param result An @c NSTextCheckingResult representing the link's location and type. - @param attributes The attributes to be added to the text in the range of the specified link. If set, the label's @c activeAttributes and @c inactiveAttributes will be applied to the link. If `nil`, no attributes are added to the link. - - @return The newly added link object. - */ -- (BITAttributedLabelLink *)addLinkWithTextCheckingResult:(NSTextCheckingResult *)result - attributes:(NSDictionary *)attributes; - -/** - Adds a link to a URL for a specified range in the label text. - - @param url The url to be linked to - @param range The range in the label text of the link. The range must not exceed the bounds of the receiver. - - @return The newly added link object. - */ -- (BITAttributedLabelLink *)addLinkToURL:(NSURL *)url - withRange:(NSRange)range; - -/** - Adds a link to an address for a specified range in the label text. - - @param addressComponents A dictionary of address components for the address to be linked to - @param range The range in the label text of the link. The range must not exceed the bounds of the receiver. - - @discussion The address component dictionary keys are described in `NSTextCheckingResult`'s "Keys for Address Components." - - @return The newly added link object. - */ -- (BITAttributedLabelLink *)addLinkToAddress:(NSDictionary *)addressComponents - withRange:(NSRange)range; - -/** - Adds a link to a phone number for a specified range in the label text. - - @param phoneNumber The phone number to be linked to. - @param range The range in the label text of the link. The range must not exceed the bounds of the receiver. - - @return The newly added link object. - */ -- (BITAttributedLabelLink *)addLinkToPhoneNumber:(NSString *)phoneNumber - withRange:(NSRange)range; - -/** - Adds a link to a date for a specified range in the label text. - - @param date The date to be linked to. - @param range The range in the label text of the link. The range must not exceed the bounds of the receiver. - - @return The newly added link object. - */ -- (BITAttributedLabelLink *)addLinkToDate:(NSDate *)date - withRange:(NSRange)range; - -/** - Adds a link to a date with a particular time zone and duration for a specified range in the label text. - - @param date The date to be linked to. - @param timeZone The time zone of the specified date. - @param duration The duration, in seconds from the specified date. - @param range The range in the label text of the link. The range must not exceed the bounds of the receiver. - - @return The newly added link object. - */ -- (BITAttributedLabelLink *)addLinkToDate:(NSDate *)date - timeZone:(NSTimeZone *)timeZone - duration:(NSTimeInterval)duration - withRange:(NSRange)range; - -/** - Adds a link to transit information for a specified range in the label text. - - @param components A dictionary containing the transit components. The currently supported keys are `NSTextCheckingAirlineKey` and `NSTextCheckingFlightKey`. - @param range The range in the label text of the link. The range must not exceed the bounds of the receiver. - - @return The newly added link object. - */ -- (BITAttributedLabelLink *)addLinkToTransitInformation:(NSDictionary *)components - withRange:(NSRange)range; - -/** - Returns whether an @c NSTextCheckingResult is found at the give point. - - @discussion This can be used together with @c UITapGestureRecognizer to tap interactions with overlapping views. - - @param point The point inside the label. - */ -- (BOOL)containslinkAtPoint:(CGPoint)point; - -/** - Returns the @c BITAttributedLabelLink at the give point if it exists. - - @discussion This can be used together with @c UIViewControllerPreviewingDelegate to peek into links. - - @param point The point inside the label. - */ -- (BITAttributedLabelLink *)linkAtPoint:(CGPoint)point; - -@end - -/** - The `BITAttributedLabelDelegate` protocol defines the messages sent to an attributed label delegate when links are tapped. All of the methods of this protocol are optional. - */ -@protocol BITAttributedLabelDelegate - -///----------------------------------- -/// @name Responding to Link Selection -///----------------------------------- -@optional - -/** - Tells the delegate that the user did select a link to a URL. - - @param label The label whose link was selected. - @param url The URL for the selected link. - */ -- (void)attributedLabel:(BITAttributedLabel *)label - didSelectLinkWithURL:(NSURL *)url; - -/** - Tells the delegate that the user did select a link to an address. - - @param label The label whose link was selected. - @param addressComponents The components of the address for the selected link. - */ -- (void)attributedLabel:(BITAttributedLabel *)label -didSelectLinkWithAddress:(NSDictionary *)addressComponents; - -/** - Tells the delegate that the user did select a link to a phone number. - - @param label The label whose link was selected. - @param phoneNumber The phone number for the selected link. - */ -- (void)attributedLabel:(BITAttributedLabel *)label -didSelectLinkWithPhoneNumber:(NSString *)phoneNumber; - -/** - Tells the delegate that the user did select a link to a date. - - @param label The label whose link was selected. - @param date The datefor the selected link. - */ -- (void)attributedLabel:(BITAttributedLabel *)label - didSelectLinkWithDate:(NSDate *)date; - -/** - Tells the delegate that the user did select a link to a date with a time zone and duration. - - @param label The label whose link was selected. - @param date The date for the selected link. - @param timeZone The time zone of the date for the selected link. - @param duration The duration, in seconds from the date for the selected link. - */ -- (void)attributedLabel:(BITAttributedLabel *)label - didSelectLinkWithDate:(NSDate *)date - timeZone:(NSTimeZone *)timeZone - duration:(NSTimeInterval)duration; - -/** - Tells the delegate that the user did select a link to transit information - - @param label The label whose link was selected. - @param components A dictionary containing the transit components. The currently supported keys are `NSTextCheckingAirlineKey` and `NSTextCheckingFlightKey`. - */ -- (void)attributedLabel:(BITAttributedLabel *)label -didSelectLinkWithTransitInformation:(NSDictionary *)components; - -/** - Tells the delegate that the user did select a link to a text checking result. - - @discussion This method is called if no other delegate method was called, which can occur by either now implementing the method in `BITAttributedLabelDelegate` corresponding to a particular link, or the link was added by passing an instance of a custom `NSTextCheckingResult` subclass into `-addLinkWithTextCheckingResult:`. - - @param label The label whose link was selected. - @param result The custom text checking result. - */ -- (void)attributedLabel:(BITAttributedLabel *)label -didSelectLinkWithTextCheckingResult:(NSTextCheckingResult *)result; - -///--------------------------------- -/// @name Responding to Long Presses -///--------------------------------- - -/** - * Long-press delegate methods include the CGPoint tapped within the label's coordinate space. - * This may be useful on iPad to present a popover from a specific origin point. - */ - -/** - Tells the delegate that the user long-pressed a link to a URL. - - @param label The label whose link was long pressed. - @param url The URL for the link. - @param point the point pressed, in the label's coordinate space - */ -- (void)attributedLabel:(BITAttributedLabel *)label -didLongPressLinkWithURL:(NSURL *)url - atPoint:(CGPoint)point; - -/** - Tells the delegate that the user long-pressed a link to an address. - - @param label The label whose link was long pressed. - @param addressComponents The components of the address for the link. - @param point the point pressed, in the label's coordinate space - */ -- (void)attributedLabel:(BITAttributedLabel *)label -didLongPressLinkWithAddress:(NSDictionary *)addressComponents - atPoint:(CGPoint)point; - -/** - Tells the delegate that the user long-pressed a link to a phone number. - - @param label The label whose link was long pressed. - @param phoneNumber The phone number for the link. - @param point the point pressed, in the label's coordinate space - */ -- (void)attributedLabel:(BITAttributedLabel *)label -didLongPressLinkWithPhoneNumber:(NSString *)phoneNumber - atPoint:(CGPoint)point; - - -/** - Tells the delegate that the user long-pressed a link to a date. - - @param label The label whose link was long pressed. - @param date The date for the selected link. - @param point the point pressed, in the label's coordinate space - */ -- (void)attributedLabel:(BITAttributedLabel *)label -didLongPressLinkWithDate:(NSDate *)date - atPoint:(CGPoint)point; - - -/** - Tells the delegate that the user long-pressed a link to a date with a time zone and duration. - - @param label The label whose link was long pressed. - @param date The date for the link. - @param timeZone The time zone of the date for the link. - @param duration The duration, in seconds from the date for the link. - @param point the point pressed, in the label's coordinate space - */ -- (void)attributedLabel:(BITAttributedLabel *)label -didLongPressLinkWithDate:(NSDate *)date - timeZone:(NSTimeZone *)timeZone - duration:(NSTimeInterval)duration - atPoint:(CGPoint)point; - - -/** - Tells the delegate that the user long-pressed a link to transit information. - - @param label The label whose link was long pressed. - @param components A dictionary containing the transit components. The currently supported keys are `NSTextCheckingAirlineKey` and `NSTextCheckingFlightKey`. - @param point the point pressed, in the label's coordinate space - */ -- (void)attributedLabel:(BITAttributedLabel *)label -didLongPressLinkWithTransitInformation:(NSDictionary *)components - atPoint:(CGPoint)point; - -/** - Tells the delegate that the user long-pressed a link to a text checking result. - - @discussion Similar to `-attributedLabel:didSelectLinkWithTextCheckingResult:`, this method is called if a link is long pressed and the delegate does not implement the method corresponding to this type of link. - - @param label The label whose link was long pressed. - @param result The custom text checking result. - @param point the point pressed, in the label's coordinate space - */ -- (void)attributedLabel:(BITAttributedLabel *)label -didLongPressLinkWithTextCheckingResult:(NSTextCheckingResult *)result - atPoint:(CGPoint)point; - -@end - -@interface BITAttributedLabelLink : NSObject - -typedef void (^BITAttributedLabelLinkBlock) (BITAttributedLabel *, BITAttributedLabelLink *); - -/** - An `NSTextCheckingResult` representing the link's location and type. - */ -@property (readonly, nonatomic, strong) NSTextCheckingResult *result; - -/** - A dictionary containing the @c NSAttributedString attributes to be applied to the link. - */ -@property (readonly, nonatomic, copy) NSDictionary *attributes; - -/** - A dictionary containing the @c NSAttributedString attributes to be applied to the link when it is in the active state. - */ -@property (readonly, nonatomic, copy) NSDictionary *activeAttributes; - -/** - A dictionary containing the @c NSAttributedString attributes to be applied to the link when it is in the inactive state, which is triggered by a change in `tintColor` in iOS 7 and later. - */ -@property (readonly, nonatomic, copy) NSDictionary *inactiveAttributes; - -/** - Additional information about a link for VoiceOver users. Has default values if the link's @c result is @c NSTextCheckingTypeLink, @c NSTextCheckingTypePhoneNumber, or @c NSTextCheckingTypeDate. - */ -@property (nonatomic, copy) NSString *accessibilityValue; - -/** - A block called when this link is tapped. - If non-nil, tapping on this link will call this block instead of the - @c BITAttributedLabelDelegate tap methods, which will not be called for this link. - */ -@property (nonatomic, copy) BITAttributedLabelLinkBlock linkTapBlock; - -/** - A block called when this link is long-pressed. - If non-nil, long pressing on this link will call this block instead of the - @c BITAttributedLabelDelegate long press methods, which will not be called for this link. - */ -@property (nonatomic, copy) BITAttributedLabelLinkBlock linkLongPressBlock; - -/** - Initializes a link using the attribute dictionaries specified. - - @param attributes The @c attributes property for the link. - @param activeAttributes The @c activeAttributes property for the link. - @param inactiveAttributes The @c inactiveAttributes property for the link. - @param result An @c NSTextCheckingResult representing the link's location and type. - - @return The initialized link object. - */ -- (instancetype)initWithAttributes:(NSDictionary *)attributes - activeAttributes:(NSDictionary *)activeAttributes - inactiveAttributes:(NSDictionary *)inactiveAttributes - textCheckingResult:(NSTextCheckingResult *)result; - -/** - Initializes a link using the attribute dictionaries set on a specified label. - - @param label The attributed label from which to inherit attribute dictionaries. - @param result An @c NSTextCheckingResult representing the link's location and type. - - @return The initialized link object. - */ -- (instancetype)initWithAttributesFromLabel:(BITAttributedLabel*)label - textCheckingResult:(NSTextCheckingResult *)result; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITAttributedLabel.m b/submodules/HockeySDK-iOS/Classes/BITAttributedLabel.m deleted file mode 100644 index 0e48cea48b..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITAttributedLabel.m +++ /dev/null @@ -1,1850 +0,0 @@ -// TTTAttributedLabel.m -// -// Copyright (c) 2011 Mattt Thompson (http://mattt.me) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_FEEDBACK - -#import "BITAttributedLabel.h" - -#import -#import -#import - -#define kBITLineBreakWordWrapTextWidthScalingFactor (M_PI / M_E) - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" -#pragma clang diagnostic ignored "-Wunused-variable" -#pragma clang diagnostic ignored "-Wunused-parameter" -#pragma clang diagnostic ignored "-Wcast-qual" -#pragma clang diagnostic ignored "-Wdouble-promotion" -#pragma clang diagnostic ignored "-Wdirect-ivar-access" -#pragma clang diagnostic ignored "-Wnullable-to-nonnull-conversion" -#pragma clang diagnostic ignored "-Wvla" - -static CGFloat const BITFLOAT_MAX = 100000; - -NSString * const kBITStrikeOutAttributeName = @"BITStrikeOutAttribute"; -NSString * const kBITBackgroundFillColorAttributeName = @"BITBackgroundFillColor"; -NSString * const kBITBackgroundFillPaddingAttributeName = @"BITBackgroundFillPadding"; -NSString * const kBITBackgroundStrokeColorAttributeName = @"BITBackgroundStrokeColor"; -NSString * const kBITBackgroundLineWidthAttributeName = @"BITBackgroundLineWidth"; -NSString * const kBITBackgroundCornerRadiusAttributeName = @"BITBackgroundCornerRadius"; - -static const NSTextAlignment BITTextAlignmentLeft = NSTextAlignmentLeft; -static const NSTextAlignment BITTextAlignmentCenter = NSTextAlignmentCenter; -static const NSTextAlignment BITTextAlignmentRight = NSTextAlignmentRight; -static const NSTextAlignment BITTextAlignmentJustified = NSTextAlignmentJustified; -static const NSTextAlignment BITTextAlignmentNatural = NSTextAlignmentNatural; - -static const NSLineBreakMode BITLineBreakByWordWrapping = NSLineBreakByWordWrapping; -static const NSLineBreakMode BITLineBreakByCharWrapping = NSLineBreakByCharWrapping; -static const NSLineBreakMode BITLineBreakByClipping = NSLineBreakByClipping; -static const NSLineBreakMode BITLineBreakByTruncatingHead = NSLineBreakByTruncatingHead; -static const NSLineBreakMode BITLineBreakByTruncatingMiddle = NSLineBreakByTruncatingMiddle; -static const NSLineBreakMode BITLineBreakByTruncatingTail = NSLineBreakByTruncatingTail; - -typedef NSTextAlignment BITTextAlignment; -typedef NSLineBreakMode BITLineBreakMode; - - -static inline CGFLOAT_TYPE CGFloat_ceil(CGFLOAT_TYPE cgfloat) { -#if CGFLOAT_IS_DOUBLE - return ceil(cgfloat); -#else - return ceilf(cgfloat); -#endif -} - -static inline CGFLOAT_TYPE CGFloat_floor(CGFLOAT_TYPE cgfloat) { -#if CGFLOAT_IS_DOUBLE - return floor(cgfloat); -#else - return floorf(cgfloat); -#endif -} - -static inline CGFLOAT_TYPE CGFloat_round(CGFLOAT_TYPE cgfloat) { -#if CGFLOAT_IS_DOUBLE - return round(cgfloat); -#else - return roundf(cgfloat); -#endif -} - -static inline CGFLOAT_TYPE CGFloat_sqrt(CGFLOAT_TYPE cgfloat) { -#if CGFLOAT_IS_DOUBLE - return sqrt(cgfloat); -#else - return sqrtf(cgfloat); -#endif -} - -static inline CGFloat BITFlushFactorForTextAlignment(NSTextAlignment textAlignment) { - switch (textAlignment) { - case BITTextAlignmentCenter: - return 0.5f; - case BITTextAlignmentRight: - return 1.0f; - case BITTextAlignmentLeft: - default: - return 0.0f; - } -} - -static inline NSDictionary * NSAttributedStringAttributesFromLabel(BITAttributedLabel *label) { - NSMutableDictionary *mutableAttributes = [NSMutableDictionary dictionary]; - - [mutableAttributes setObject:label.font forKey:(NSString *)kCTFontAttributeName]; - [mutableAttributes setObject:label.textColor forKey:(NSString *)kCTForegroundColorAttributeName]; - [mutableAttributes setObject:@(label.kern) forKey:(NSString *)kCTKernAttributeName]; - - NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init]; - paragraphStyle.alignment = label.textAlignment; - paragraphStyle.lineSpacing = label.lineSpacing; - paragraphStyle.minimumLineHeight = label.minimumLineHeight > 0 ? label.minimumLineHeight : label.font.lineHeight * label.lineHeightMultiple; - paragraphStyle.maximumLineHeight = label.maximumLineHeight > 0 ? label.maximumLineHeight : label.font.lineHeight * label.lineHeightMultiple; - paragraphStyle.lineHeightMultiple = label.lineHeightMultiple; - paragraphStyle.firstLineHeadIndent = label.firstLineIndent; - - if (label.numberOfLines == 1) { - paragraphStyle.lineBreakMode = label.lineBreakMode; - } else { - paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping; - } - - [mutableAttributes setObject:paragraphStyle forKey:(NSString *)kCTParagraphStyleAttributeName]; - - return [NSDictionary dictionaryWithDictionary:mutableAttributes]; -} - -static inline CGColorRef CGColorRefFromColor(id color); -static inline NSDictionary * convertNSAttributedStringAttributesToCTAttributes(NSDictionary *attributes); - -static inline NSAttributedString * NSAttributedStringByScalingFontSize(NSAttributedString *attributedString, CGFloat scale) { - NSMutableAttributedString *mutableAttributedString = [attributedString mutableCopy]; - [mutableAttributedString enumerateAttribute:(NSString *)kCTFontAttributeName inRange:NSMakeRange(0, [mutableAttributedString length]) options:0 usingBlock:^(id value, NSRange range, BOOL * __unused stop) { - UIFont *font = (UIFont *)value; - if (font) { - NSString *fontName; - CGFloat pointSize; - - if ([font isKindOfClass:[UIFont class]]) { - fontName = font.fontName; - pointSize = font.pointSize; - } else { - fontName = (NSString *)CFBridgingRelease(CTFontCopyName((__bridge CTFontRef)font, kCTFontPostScriptNameKey)); - pointSize = CTFontGetSize((__bridge CTFontRef)font); - } - - [mutableAttributedString removeAttribute:(NSString *)kCTFontAttributeName range:range]; - CTFontRef fontRef = CTFontCreateWithName((__bridge CFStringRef)fontName, CGFloat_floor(pointSize * scale), NULL); - [mutableAttributedString addAttribute:(NSString *)kCTFontAttributeName value:(__bridge id)fontRef range:range]; - CFRelease(fontRef); - } - }]; - - return mutableAttributedString; -} - -static inline NSAttributedString * NSAttributedStringBySettingColorFromContext(NSAttributedString *attributedString, UIColor *color) { - if (!color) { - return attributedString; - } - - NSMutableAttributedString *mutableAttributedString = [attributedString mutableCopy]; - [mutableAttributedString enumerateAttribute:(NSString *)kCTForegroundColorFromContextAttributeName inRange:NSMakeRange(0, [mutableAttributedString length]) options:0 usingBlock:^(id value, NSRange range, __unused BOOL *stop) { - BOOL usesColorFromContext = (BOOL)value; - if (usesColorFromContext) { - [mutableAttributedString setAttributes:[NSDictionary dictionaryWithObject:color forKey:(NSString *)kCTForegroundColorAttributeName] range:range]; - [mutableAttributedString removeAttribute:(NSString *)kCTForegroundColorFromContextAttributeName range:range]; - } - }]; - - return mutableAttributedString; -} - -static inline CGSize CTFramesetterSuggestFrameSizeForAttributedStringWithConstraints(CTFramesetterRef framesetter, NSAttributedString *attributedString, CGSize size, NSUInteger numberOfLines) { - CFRange rangeToSize = CFRangeMake(0, (CFIndex)[attributedString length]); - CGSize constraints = CGSizeMake(size.width, BITFLOAT_MAX); - - if (numberOfLines == 1) { - // If there is one line, the size that fits is the full width of the line - constraints = CGSizeMake(BITFLOAT_MAX, BITFLOAT_MAX); - } else if (numberOfLines > 0) { - // If the line count of the label more than 1, limit the range to size to the number of lines that have been set - CGMutablePathRef path = CGPathCreateMutable(); - CGPathAddRect(path, NULL, CGRectMake(0.0f, 0.0f, constraints.width, BITFLOAT_MAX)); - CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL); - CFArrayRef lines = CTFrameGetLines(frame); - - if (CFArrayGetCount(lines) > 0) { - NSInteger lastVisibleLineIndex = MIN((CFIndex)numberOfLines, CFArrayGetCount(lines)) - 1; - CTLineRef lastVisibleLine = CFArrayGetValueAtIndex(lines, lastVisibleLineIndex); - - CFRange rangeToLayout = CTLineGetStringRange(lastVisibleLine); - rangeToSize = CFRangeMake(0, rangeToLayout.location + rangeToLayout.length); - } - - CFRelease(frame); - CGPathRelease(path); - } - - CGSize suggestedSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, rangeToSize, NULL, constraints, NULL); - - return CGSizeMake(CGFloat_ceil(suggestedSize.width), CGFloat_ceil(suggestedSize.height)); -} - -@interface BITAccessibilityElement : UIAccessibilityElement -@property (nonatomic, weak) UIView *superview; -@property (nonatomic, assign) CGRect boundingRect; -@end - -@implementation BITAccessibilityElement - -- (CGRect)accessibilityFrame { - return UIAccessibilityConvertFrameToScreenCoordinates(self.boundingRect, self.superview); -} - -@end - -@interface BITAttributedLabel () -@property (readwrite, nonatomic, copy) NSAttributedString *inactiveAttributedText; -@property (readwrite, nonatomic, copy) NSAttributedString *renderedAttributedText; -@property (readwrite, atomic, strong) NSDataDetector *dataDetector; -@property (readwrite, nonatomic, strong) NSArray *linkModels; -@property (readwrite, nonatomic, strong) BITAttributedLabelLink *activeLink; -@property (readwrite, nonatomic, strong) NSArray *accessibilityElements; - -- (void) longPressGestureDidFire:(UILongPressGestureRecognizer *)sender; -@end - -@implementation BITAttributedLabel { -@private - BOOL _needsFramesetter; - CTFramesetterRef _framesetter; - CTFramesetterRef _highlightFramesetter; -} - -@dynamic text; -@synthesize attributedText = _attributedText; - -#ifndef kCFCoreFoundationVersionNumber_iOS_7_0 -#define kCFCoreFoundationVersionNumber_iOS_7_0 847.2 -#endif - -+ (void)load { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - if (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0) { - Class class = [self class]; - Class superclass = class_getSuperclass(class); - - NSArray *strings = @[ - NSStringFromSelector(@selector(isAccessibilityElement)), - NSStringFromSelector(@selector(accessibilityElementCount)), - NSStringFromSelector(@selector(accessibilityElementAtIndex:)), - NSStringFromSelector(@selector(indexOfAccessibilityElement:)), - ]; - - for (NSString *string in strings) { - SEL selector = NSSelectorFromString(string); - IMP superImplementation = class_getMethodImplementation(superclass, selector); - Method method = class_getInstanceMethod(class, selector); - const char *types = method_getTypeEncoding(method); - class_replaceMethod(class, selector, superImplementation, types); - } - } - }); -} - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (!self) { - return nil; - } - - [self commonInit]; - - return self; -} - -- (void)commonInit { - self.userInteractionEnabled = YES; -#if !TARGET_OS_TV - self.multipleTouchEnabled = NO; -#endif - - self.textInsets = UIEdgeInsetsZero; - self.lineHeightMultiple = 1.0f; - - self.linkModels = [NSArray array]; - - self.linkBackgroundEdgeInset = UIEdgeInsetsMake(0.0f, -1.0f, 0.0f, -1.0f); - - NSMutableDictionary *mutableLinkAttributes = [NSMutableDictionary dictionary]; - [mutableLinkAttributes setObject:[NSNumber numberWithBool:YES] forKey:(NSString *)kCTUnderlineStyleAttributeName]; - - NSMutableDictionary *mutableActiveLinkAttributes = [NSMutableDictionary dictionary]; - [mutableActiveLinkAttributes setObject:[NSNumber numberWithBool:NO] forKey:(NSString *)kCTUnderlineStyleAttributeName]; - - NSMutableDictionary *mutableInactiveLinkAttributes = [NSMutableDictionary dictionary]; - [mutableInactiveLinkAttributes setObject:[NSNumber numberWithBool:NO] forKey:(NSString *)kCTUnderlineStyleAttributeName]; - - if ([NSMutableParagraphStyle class]) { - [mutableLinkAttributes setObject:[UIColor blueColor] forKey:(NSString *)kCTForegroundColorAttributeName]; - [mutableActiveLinkAttributes setObject:[UIColor redColor] forKey:(NSString *)kCTForegroundColorAttributeName]; - [mutableInactiveLinkAttributes setObject:[UIColor grayColor] forKey:(NSString *)kCTForegroundColorAttributeName]; - } else { - [mutableLinkAttributes setObject:(__bridge id)[[UIColor blueColor] CGColor] forKey:(NSString *)kCTForegroundColorAttributeName]; - [mutableActiveLinkAttributes setObject:(__bridge id)[[UIColor redColor] CGColor] forKey:(NSString *)kCTForegroundColorAttributeName]; - [mutableInactiveLinkAttributes setObject:(__bridge id)[[UIColor grayColor] CGColor] forKey:(NSString *)kCTForegroundColorAttributeName]; - } - - self.linkAttributes = [NSDictionary dictionaryWithDictionary:mutableLinkAttributes]; - self.activeLinkAttributes = [NSDictionary dictionaryWithDictionary:mutableActiveLinkAttributes]; - self.inactiveLinkAttributes = [NSDictionary dictionaryWithDictionary:mutableInactiveLinkAttributes]; - _extendsLinkTouchArea = NO; - _longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self - action:@selector(longPressGestureDidFire:)]; - self.longPressGestureRecognizer.delegate = self; - [self addGestureRecognizer:self.longPressGestureRecognizer]; -} - -- (void)dealloc { - if (_framesetter) { - CFRelease(_framesetter); - } - - if (_highlightFramesetter) { - CFRelease(_highlightFramesetter); - } - - if (_longPressGestureRecognizer) { - [self removeGestureRecognizer:_longPressGestureRecognizer]; - } -} - -#pragma mark - - -+ (CGSize)sizeThatFitsAttributedString:(NSAttributedString *)attributedString - withConstraints:(CGSize)size - limitedToNumberOfLines:(NSUInteger)numberOfLines -{ - if (!attributedString || attributedString.length == 0) { - return CGSizeZero; - } - - CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)attributedString); - - CGSize calculatedSize = CTFramesetterSuggestFrameSizeForAttributedStringWithConstraints(framesetter, attributedString, size, numberOfLines); - - CFRelease(framesetter); - - return calculatedSize; -} - -#pragma mark - - -- (void)setAttributedText:(NSAttributedString *)text { - if ([text isEqualToAttributedString:_attributedText]) { - return; - } - - _attributedText = [text copy]; - - [self setNeedsFramesetter]; - [self setNeedsDisplay]; - - if ([self respondsToSelector:@selector(invalidateIntrinsicContentSize)]) { - [self invalidateIntrinsicContentSize]; - } - - [super setText:[self.attributedText string]]; -} - -- (NSAttributedString *)renderedAttributedText { - if (!_renderedAttributedText) { - NSMutableAttributedString *fullString = [[NSMutableAttributedString alloc] initWithAttributedString:self.attributedText]; - - if (self.attributedTruncationToken) { - [fullString appendAttributedString:self.attributedTruncationToken]; - } - - NSAttributedString *string = [[NSAttributedString alloc] initWithAttributedString:fullString]; - self.renderedAttributedText = NSAttributedStringBySettingColorFromContext(string, self.textColor); - } - - return _renderedAttributedText; -} - -- (NSArray *) links { - return [_linkModels valueForKey:@"result"]; -} - -- (void)setLinkModels:(NSArray *)linkModels { - _linkModels = linkModels; - - self.accessibilityElements = nil; -} - -- (void)setNeedsFramesetter { - // Reset the rendered attributed text so it has a chance to regenerate - self.renderedAttributedText = nil; - - _needsFramesetter = YES; -} - -- (CTFramesetterRef)framesetter { - if (_needsFramesetter) { - @synchronized(self) { - CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)self.renderedAttributedText); - [self setFramesetter:framesetter]; - [self setHighlightFramesetter:nil]; - _needsFramesetter = NO; - - if (framesetter) { - CFRelease(framesetter); - } - } - } - - return _framesetter; -} - -- (void)setFramesetter:(CTFramesetterRef)framesetter { - if (framesetter) { - CFRetain(framesetter); - } - - if (_framesetter) { - CFRelease(_framesetter); - } - - _framesetter = framesetter; -} - -- (CTFramesetterRef)highlightFramesetter { - return _highlightFramesetter; -} - -- (void)setHighlightFramesetter:(CTFramesetterRef)highlightFramesetter { - if (highlightFramesetter) { - CFRetain(highlightFramesetter); - } - - if (_highlightFramesetter) { - CFRelease(_highlightFramesetter); - } - - _highlightFramesetter = highlightFramesetter; -} - -#pragma mark - - -- (void)setEnabledTextCheckingTypes:(NSTextCheckingTypes)enabledTextCheckingTypes { - if (self.enabledTextCheckingTypes == enabledTextCheckingTypes) { - return; - } - - _enabledTextCheckingTypes = enabledTextCheckingTypes; - - // one detector instance per type (combination), fast reuse e.g. in cells - static NSMutableDictionary *dataDetectorsByType = nil; - - if (!dataDetectorsByType) { - dataDetectorsByType = [NSMutableDictionary dictionary]; - } - - if (enabledTextCheckingTypes) { - if (![dataDetectorsByType objectForKey:@(enabledTextCheckingTypes)]) { - NSDataDetector *detector = [NSDataDetector dataDetectorWithTypes:enabledTextCheckingTypes - error:nil]; - if (detector) { - [dataDetectorsByType setObject:detector forKey:@(enabledTextCheckingTypes)]; - } - } - self.dataDetector = [dataDetectorsByType objectForKey:@(enabledTextCheckingTypes)]; - } else { - self.dataDetector = nil; - } -} - -- (void)addLink:(BITAttributedLabelLink *)link { - [self addLinks:@[link]]; -} - -- (void)addLinks:(NSArray *)links { - NSMutableArray *mutableLinkModels = [NSMutableArray arrayWithArray:self.linkModels]; - - NSMutableAttributedString *mutableAttributedString = [self.attributedText mutableCopy]; - - for (BITAttributedLabelLink *link in links) { - if (link.attributes) { - [mutableAttributedString addAttributes:link.attributes range:link.result.range]; - } - } - - self.attributedText = mutableAttributedString; - [self setNeedsDisplay]; - - [mutableLinkModels addObjectsFromArray:links]; - - self.linkModels = [NSArray arrayWithArray:mutableLinkModels]; -} - -- (BITAttributedLabelLink *)addLinkWithTextCheckingResult:(NSTextCheckingResult *)result - attributes:(NSDictionary *)attributes -{ - return [self addLinksWithTextCheckingResults:@[result] attributes:attributes].firstObject; -} - -- (NSArray *)addLinksWithTextCheckingResults:(NSArray *)results - attributes:(NSDictionary *)attributes -{ - NSMutableArray *links = [NSMutableArray array]; - - for (NSTextCheckingResult *result in results) { - NSDictionary *activeAttributes = attributes ? self.activeLinkAttributes : nil; - NSDictionary *inactiveAttributes = attributes ? self.inactiveLinkAttributes : nil; - - BITAttributedLabelLink *link = [[BITAttributedLabelLink alloc] initWithAttributes:attributes - activeAttributes:activeAttributes - inactiveAttributes:inactiveAttributes - textCheckingResult:result]; - - [links addObject:link]; - } - - [self addLinks:links]; - - return links; -} - -- (BITAttributedLabelLink *)addLinkWithTextCheckingResult:(NSTextCheckingResult *)result { - return [self addLinkWithTextCheckingResult:result attributes:self.linkAttributes]; -} - -- (BITAttributedLabelLink *)addLinkToURL:(NSURL *)url - withRange:(NSRange)range -{ - return [self addLinkWithTextCheckingResult:[NSTextCheckingResult linkCheckingResultWithRange:range URL:url]]; -} - -- (BITAttributedLabelLink *)addLinkToAddress:(NSDictionary *)addressComponents - withRange:(NSRange)range -{ - return [self addLinkWithTextCheckingResult:[NSTextCheckingResult addressCheckingResultWithRange:range components:addressComponents]]; -} - -- (BITAttributedLabelLink *)addLinkToPhoneNumber:(NSString *)phoneNumber - withRange:(NSRange)range -{ - return [self addLinkWithTextCheckingResult:[NSTextCheckingResult phoneNumberCheckingResultWithRange:range phoneNumber:phoneNumber]]; -} - -- (BITAttributedLabelLink *)addLinkToDate:(NSDate *)date - withRange:(NSRange)range -{ - return [self addLinkWithTextCheckingResult:[NSTextCheckingResult dateCheckingResultWithRange:range date:date]]; -} - -- (BITAttributedLabelLink *)addLinkToDate:(NSDate *)date - timeZone:(NSTimeZone *)timeZone - duration:(NSTimeInterval)duration - withRange:(NSRange)range -{ - return [self addLinkWithTextCheckingResult:[NSTextCheckingResult dateCheckingResultWithRange:range date:date timeZone:timeZone duration:duration]]; -} - -- (BITAttributedLabelLink *)addLinkToTransitInformation:(NSDictionary *)components - withRange:(NSRange)range -{ - return [self addLinkWithTextCheckingResult:[NSTextCheckingResult transitInformationCheckingResultWithRange:range components:components]]; -} - -#pragma mark - - -- (BOOL)containslinkAtPoint:(CGPoint)point { - return [self linkAtPoint:point] != nil; -} - -- (BITAttributedLabelLink *)linkAtPoint:(CGPoint)point { - - // Stop quickly if none of the points to be tested are in the bounds. - if (!CGRectContainsPoint(CGRectInset(self.bounds, -15.f, -15.f), point) || self.links.count == 0) { - return nil; - } - - BITAttributedLabelLink *result = [self linkAtCharacterIndex:[self characterIndexAtPoint:point]]; - - if (!result && self.extendsLinkTouchArea) { - result = [self linkAtRadius:2.5f aroundPoint:point] - ?: [self linkAtRadius:5.f aroundPoint:point] - ?: [self linkAtRadius:7.5f aroundPoint:point] - ?: [self linkAtRadius:12.5f aroundPoint:point] - ?: [self linkAtRadius:15.f aroundPoint:point]; - } - - return result; -} - -- (BITAttributedLabelLink *)linkAtRadius:(const CGFloat)radius aroundPoint:(CGPoint)point { - const CGFloat diagonal = CGFloat_sqrt(2 * radius * radius); - const CGPoint deltas[] = { - CGPointMake(0, -radius), CGPointMake(0, radius), // Above and below - CGPointMake(-radius, 0), CGPointMake(radius, 0), // Beside - CGPointMake(-diagonal, -diagonal), CGPointMake(-diagonal, diagonal), - CGPointMake(diagonal, diagonal), CGPointMake(diagonal, -diagonal) // Diagonal - }; - const size_t count = sizeof(deltas) / sizeof(CGPoint); - - BITAttributedLabelLink *link = nil; - - for (NSUInteger i = 0; i < count && link.result == nil; i ++) { - CGPoint currentPoint = CGPointMake(point.x + deltas[i].x, point.y + deltas[i].y); - link = [self linkAtCharacterIndex:[self characterIndexAtPoint:currentPoint]]; - } - - return link; -} - -- (BITAttributedLabelLink *)linkAtCharacterIndex:(CFIndex)idx { - // Do not enumerate if the index is outside of the bounds of the text. - if (!NSLocationInRange((NSUInteger)idx, NSMakeRange(0, self.attributedText.length))) { - return nil; - } - - NSEnumerator *enumerator = [self.linkModels reverseObjectEnumerator]; - BITAttributedLabelLink *link = nil; - while ((link = [enumerator nextObject])) { - if (NSLocationInRange((NSUInteger)idx, link.result.range)) { - return link; - } - } - - return nil; -} - -- (CFIndex)characterIndexAtPoint:(CGPoint)p { - if (!CGRectContainsPoint(self.bounds, p)) { - return NSNotFound; - } - - CGRect textRect = [self textRectForBounds:self.bounds limitedToNumberOfLines:self.numberOfLines]; - if (!CGRectContainsPoint(textRect, p)) { - return NSNotFound; - } - - // Offset tap coordinates by textRect origin to make them relative to the origin of frame - p = CGPointMake(p.x - textRect.origin.x, p.y - textRect.origin.y); - // Convert tap coordinates (start at top left) to CT coordinates (start at bottom left) - p = CGPointMake(p.x, textRect.size.height - p.y); - - CGMutablePathRef path = CGPathCreateMutable(); - CGPathAddRect(path, NULL, textRect); - CTFrameRef frame = CTFramesetterCreateFrame([self framesetter], CFRangeMake(0, (CFIndex)[self.attributedText length]), path, NULL); - if (frame == NULL) { - CGPathRelease(path); - return NSNotFound; - } - - CFArrayRef lines = CTFrameGetLines(frame); - NSInteger numberOfLines = self.numberOfLines > 0 ? MIN(self.numberOfLines, CFArrayGetCount(lines)) : CFArrayGetCount(lines); - if (numberOfLines == 0) { - CFRelease(frame); - CGPathRelease(path); - return NSNotFound; - } - - CFIndex idx = NSNotFound; - - CGPoint lineOrigins[numberOfLines]; - CTFrameGetLineOrigins(frame, CFRangeMake(0, numberOfLines), lineOrigins); - - for (CFIndex lineIndex = 0; lineIndex < numberOfLines; lineIndex++) { - CGPoint lineOrigin = lineOrigins[lineIndex]; - CTLineRef line = CFArrayGetValueAtIndex(lines, lineIndex); - - // Get bounding information of line - CGFloat ascent = 0.0f, descent = 0.0f, leading = 0.0f; - CGFloat width = (CGFloat)CTLineGetTypographicBounds(line, &ascent, &descent, &leading); - CGFloat yMin = (CGFloat)floor(lineOrigin.y - descent); - CGFloat yMax = (CGFloat)ceil(lineOrigin.y + ascent); - - // Apply penOffset using flushFactor for horizontal alignment to set lineOrigin since this is the horizontal offset from drawFramesetter - CGFloat flushFactor = BITFlushFactorForTextAlignment(self.textAlignment); - CGFloat penOffset = (CGFloat)CTLineGetPenOffsetForFlush(line, flushFactor, textRect.size.width); - lineOrigin.x = penOffset; - - // Check if we've already passed the line - if (p.y > yMax) { - break; - } - // Check if the point is within this line vertically - if (p.y >= yMin) { - // Check if the point is within this line horizontally - if (p.x >= lineOrigin.x && p.x <= lineOrigin.x + width) { - // Convert CT coordinates to line-relative coordinates - CGPoint relativePoint = CGPointMake(p.x - lineOrigin.x, p.y - lineOrigin.y); - idx = CTLineGetStringIndexForPosition(line, relativePoint); - break; - } - } - } - - CFRelease(frame); - CGPathRelease(path); - - return idx; -} - -- (CGRect)boundingRectForCharacterRange:(NSRange)range { - NSMutableAttributedString *mutableAttributedString = [self.attributedText mutableCopy]; - - NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:mutableAttributedString]; - - NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init]; - [textStorage addLayoutManager:layoutManager]; - - NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:self.bounds.size]; - [layoutManager addTextContainer:textContainer]; - - NSRange glyphRange; - [layoutManager characterRangeForGlyphRange:range actualGlyphRange:&glyphRange]; - - return [layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer]; -} - -- (void)drawFramesetter:(CTFramesetterRef)framesetter - attributedString:(NSAttributedString *)attributedString - textRange:(CFRange)textRange - inRect:(CGRect)rect - context:(CGContextRef)c -{ - CGMutablePathRef path = CGPathCreateMutable(); - CGPathAddRect(path, NULL, rect); - CTFrameRef frame = CTFramesetterCreateFrame(framesetter, textRange, path, NULL); - - [self drawBackground:frame inRect:rect context:c]; - - CFArrayRef lines = CTFrameGetLines(frame); - NSInteger numberOfLines = self.numberOfLines > 0 ? MIN(self.numberOfLines, CFArrayGetCount(lines)) : CFArrayGetCount(lines); - BOOL truncateLastLine = (self.lineBreakMode == BITLineBreakByTruncatingHead || self.lineBreakMode == BITLineBreakByTruncatingMiddle || self.lineBreakMode == BITLineBreakByTruncatingTail); - - CGPoint lineOrigins[numberOfLines]; - CTFrameGetLineOrigins(frame, CFRangeMake(0, numberOfLines), lineOrigins); - - for (CFIndex lineIndex = 0; lineIndex < numberOfLines; lineIndex++) { - CGPoint lineOrigin = lineOrigins[lineIndex]; - CGContextSetTextPosition(c, lineOrigin.x, lineOrigin.y); - CTLineRef line = CFArrayGetValueAtIndex(lines, lineIndex); - - CGFloat descent = 0.0f; - CTLineGetTypographicBounds((CTLineRef)line, NULL, &descent, NULL); - - // Adjust pen offset for flush depending on text alignment - CGFloat flushFactor = BITFlushFactorForTextAlignment(self.textAlignment); - - if (lineIndex == numberOfLines - 1 && truncateLastLine) { - // Check if the range of text in the last line reaches the end of the full attributed string - CFRange lastLineRange = CTLineGetStringRange(line); - - if (!(lastLineRange.length == 0 && lastLineRange.location == 0) && lastLineRange.location + lastLineRange.length < textRange.location + textRange.length) { - // Get correct truncationType and attribute position - CTLineTruncationType truncationType; - CFIndex truncationAttributePosition = lastLineRange.location; - BITLineBreakMode lineBreakMode = self.lineBreakMode; - - // Multiple lines, only use UILineBreakModeTailTruncation - if (numberOfLines != 1) { - lineBreakMode = BITLineBreakByTruncatingTail; - } - - switch (lineBreakMode) { - case BITLineBreakByTruncatingHead: - truncationType = kCTLineTruncationStart; - break; - case BITLineBreakByTruncatingMiddle: - truncationType = kCTLineTruncationMiddle; - truncationAttributePosition += (lastLineRange.length / 2); - break; - case BITLineBreakByTruncatingTail: - default: - truncationType = kCTLineTruncationEnd; - truncationAttributePosition += (lastLineRange.length - 1); - break; - } - - NSAttributedString *attributedTruncationString = self.attributedTruncationToken; - if (!attributedTruncationString) { - NSString *truncationTokenString = @"\u2026"; // Unicode Character 'HORIZONTAL ELLIPSIS' (U+2026) - - NSDictionary *truncationTokenStringAttributes = truncationTokenStringAttributes = [attributedString attributesAtIndex:(NSUInteger)truncationAttributePosition effectiveRange:NULL]; - - attributedTruncationString = [[NSAttributedString alloc] initWithString:truncationTokenString attributes:truncationTokenStringAttributes]; - } - CTLineRef truncationToken = CTLineCreateWithAttributedString((__bridge CFAttributedStringRef)attributedTruncationString); - - // Append truncationToken to the string - // because if string isn't too long, CT won't add the truncationToken on its own. - // There is no chance of a double truncationToken because CT only adds the - // token if it removes characters (and the one we add will go first) - NSMutableAttributedString *truncationString = [[NSMutableAttributedString alloc] initWithAttributedString: - [attributedString attributedSubstringFromRange: - NSMakeRange((NSUInteger)lastLineRange.location, - (NSUInteger)lastLineRange.length)]]; - if (lastLineRange.length > 0) { - // Remove any newline at the end (we don't want newline space between the text and the truncation token). There can only be one, because the second would be on the next line. - unichar lastCharacter = [[truncationString string] characterAtIndex:(NSUInteger)(lastLineRange.length - 1)]; - if ([[NSCharacterSet newlineCharacterSet] characterIsMember:lastCharacter]) { - [truncationString deleteCharactersInRange:NSMakeRange((NSUInteger)(lastLineRange.length - 1), 1)]; - } - } - [truncationString appendAttributedString:attributedTruncationString]; - CTLineRef truncationLine = CTLineCreateWithAttributedString((__bridge CFAttributedStringRef)truncationString); - - // Truncate the line in case it is too long. - CTLineRef truncatedLine = CTLineCreateTruncatedLine(truncationLine, rect.size.width, truncationType, truncationToken); - if (!truncatedLine) { - // If the line is not as wide as the truncationToken, truncatedLine is NULL - truncatedLine = CFRetain(truncationToken); - } - - CGFloat penOffset = (CGFloat)CTLineGetPenOffsetForFlush(truncatedLine, flushFactor, rect.size.width); - CGContextSetTextPosition(c, penOffset, lineOrigin.y - descent - self.font.descender); - - CTLineDraw(truncatedLine, c); - - NSRange linkRange; - if ([attributedTruncationString attribute:NSLinkAttributeName atIndex:0 effectiveRange:&linkRange]) { - NSRange tokenRange = [truncationString.string rangeOfString:attributedTruncationString.string]; - NSRange tokenLinkRange = NSMakeRange((NSUInteger)(lastLineRange.location+lastLineRange.length)-tokenRange.length, (NSUInteger)tokenRange.length); - - [self addLinkToURL:[attributedTruncationString attribute:NSLinkAttributeName atIndex:0 effectiveRange:&linkRange] withRange:tokenLinkRange]; - } - - CFRelease(truncatedLine); - CFRelease(truncationLine); - CFRelease(truncationToken); - } else { - CGFloat penOffset = (CGFloat)CTLineGetPenOffsetForFlush(line, flushFactor, rect.size.width); - CGContextSetTextPosition(c, penOffset, lineOrigin.y - descent - self.font.descender); - CTLineDraw(line, c); - } - } else { - CGFloat penOffset = (CGFloat)CTLineGetPenOffsetForFlush(line, flushFactor, rect.size.width); - CGContextSetTextPosition(c, penOffset, lineOrigin.y - descent - self.font.descender); - CTLineDraw(line, c); - } - } - - [self drawStrike:frame inRect:rect context:c]; - - CFRelease(frame); - CGPathRelease(path); -} - -- (void)drawBackground:(CTFrameRef)frame - inRect:(CGRect)rect - context:(CGContextRef)c -{ - NSArray *lines = (__bridge NSArray *)CTFrameGetLines(frame); - CGPoint origins[[lines count]]; - CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), origins); - - CFIndex lineIndex = 0; - for (id line in lines) { - CGFloat ascent = 0.0f, descent = 0.0f, leading = 0.0f; - CGFloat width = (CGFloat)CTLineGetTypographicBounds((__bridge CTLineRef)line, &ascent, &descent, &leading) ; - - for (id glyphRun in (__bridge NSArray *)CTLineGetGlyphRuns((__bridge CTLineRef)line)) { - NSDictionary *attributes = (__bridge NSDictionary *)CTRunGetAttributes((__bridge CTRunRef) glyphRun); - CGColorRef strokeColor = CGColorRefFromColor([attributes objectForKey:kBITBackgroundStrokeColorAttributeName]); - CGColorRef fillColor = CGColorRefFromColor([attributes objectForKey:kBITBackgroundFillColorAttributeName]); - UIEdgeInsets fillPadding = [[attributes objectForKey:kBITBackgroundFillPaddingAttributeName] UIEdgeInsetsValue]; - CGFloat cornerRadius = [[attributes objectForKey:kBITBackgroundCornerRadiusAttributeName] floatValue]; - CGFloat lineWidth = [[attributes objectForKey:kBITBackgroundLineWidthAttributeName] floatValue]; - - if (strokeColor || fillColor) { - CGRect runBounds = CGRectZero; - CGFloat runAscent = 0.0f; - CGFloat runDescent = 0.0f; - - runBounds.size.width = (CGFloat)CTRunGetTypographicBounds((__bridge CTRunRef)glyphRun, CFRangeMake(0, 0), &runAscent, &runDescent, NULL) + fillPadding.left + fillPadding.right; - runBounds.size.height = runAscent + runDescent + fillPadding.top + fillPadding.bottom; - - CGFloat xOffset = 0.0f; - CFRange glyphRange = CTRunGetStringRange((__bridge CTRunRef)glyphRun); - switch (CTRunGetStatus((__bridge CTRunRef)glyphRun)) { - case kCTRunStatusRightToLeft: - xOffset = CTLineGetOffsetForStringIndex((__bridge CTLineRef)line, glyphRange.location + glyphRange.length, NULL); - break; - default: - xOffset = CTLineGetOffsetForStringIndex((__bridge CTLineRef)line, glyphRange.location, NULL); - break; - } - - runBounds.origin.x = origins[lineIndex].x + rect.origin.x + xOffset - fillPadding.left - rect.origin.x; - runBounds.origin.y = origins[lineIndex].y + rect.origin.y - fillPadding.bottom - rect.origin.y; - runBounds.origin.y -= runDescent; - - // Don't draw higlightedLinkBackground too far to the right - if (CGRectGetWidth(runBounds) > width) { - runBounds.size.width = width; - } - - CGPathRef path = [[UIBezierPath bezierPathWithRoundedRect:CGRectInset(UIEdgeInsetsInsetRect(runBounds, self.linkBackgroundEdgeInset), lineWidth, lineWidth) cornerRadius:cornerRadius] CGPath]; - - CGContextSetLineJoin(c, kCGLineJoinRound); - - if (fillColor) { - CGContextSetFillColorWithColor(c, fillColor); - CGContextAddPath(c, path); - CGContextFillPath(c); - } - - if (strokeColor) { - CGContextSetStrokeColorWithColor(c, strokeColor); - CGContextAddPath(c, path); - CGContextStrokePath(c); - } - } - } - - lineIndex++; - } -} - -- (void)drawStrike:(CTFrameRef)frame - inRect:(__unused CGRect)rect - context:(CGContextRef)c -{ - NSArray *lines = (__bridge NSArray *)CTFrameGetLines(frame); - CGPoint origins[[lines count]]; - CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), origins); - - CFIndex lineIndex = 0; - for (id line in lines) { - CGFloat ascent = 0.0f, descent = 0.0f, leading = 0.0f; - CGFloat width = (CGFloat)CTLineGetTypographicBounds((__bridge CTLineRef)line, &ascent, &descent, &leading) ; - - for (id glyphRun in (__bridge NSArray *)CTLineGetGlyphRuns((__bridge CTLineRef)line)) { - NSDictionary *attributes = (__bridge NSDictionary *)CTRunGetAttributes((__bridge CTRunRef) glyphRun); - BOOL strikeOut = [[attributes objectForKey:kBITStrikeOutAttributeName] boolValue]; - NSInteger superscriptStyle = [[attributes objectForKey:(id)kCTSuperscriptAttributeName] integerValue]; - - if (strikeOut) { - CGRect runBounds = CGRectZero; - CGFloat runAscent = 0.0f; - CGFloat runDescent = 0.0f; - - runBounds.size.width = (CGFloat)CTRunGetTypographicBounds((__bridge CTRunRef)glyphRun, CFRangeMake(0, 0), &runAscent, &runDescent, NULL); - runBounds.size.height = runAscent + runDescent; - - CGFloat xOffset = 0.0f; - CFRange glyphRange = CTRunGetStringRange((__bridge CTRunRef)glyphRun); - switch (CTRunGetStatus((__bridge CTRunRef)glyphRun)) { - case kCTRunStatusRightToLeft: - xOffset = CTLineGetOffsetForStringIndex((__bridge CTLineRef)line, glyphRange.location + glyphRange.length, NULL); - break; - default: - xOffset = CTLineGetOffsetForStringIndex((__bridge CTLineRef)line, glyphRange.location, NULL); - break; - } - runBounds.origin.x = origins[lineIndex].x + xOffset; - runBounds.origin.y = origins[lineIndex].y; - runBounds.origin.y -= runDescent; - - // Don't draw strikeout too far to the right - if (CGRectGetWidth(runBounds) > width) { - runBounds.size.width = width; - } - - switch (superscriptStyle) { - case 1: - runBounds.origin.y -= runAscent * 0.47f; - break; - case -1: - runBounds.origin.y += runAscent * 0.25f; - break; - default: - break; - } - - // Use text color, or default to black - id color = [attributes objectForKey:(id)kCTForegroundColorAttributeName]; - if (color) { - CGContextSetStrokeColorWithColor(c, CGColorRefFromColor(color)); - } else { - CGContextSetGrayStrokeColor(c, 0.0f, 1.0); - } - - CTFontRef font = CTFontCreateWithName((__bridge CFStringRef)self.font.fontName, self.font.pointSize, NULL); - CGContextSetLineWidth(c, CTFontGetUnderlineThickness(font)); - CFRelease(font); - - CGFloat y = CGFloat_round(runBounds.origin.y + runBounds.size.height / 2.0f); - CGContextMoveToPoint(c, runBounds.origin.x, y); - CGContextAddLineToPoint(c, runBounds.origin.x + runBounds.size.width, y); - - CGContextStrokePath(c); - } - } - - lineIndex++; - } -} - -#pragma mark - BITAttributedLabel - -- (void)setText:(id)text { - NSParameterAssert(!text || [text isKindOfClass:[NSAttributedString class]] || [text isKindOfClass:[NSString class]]); - - if ([text isKindOfClass:[NSString class]]) { - [self setText:text afterInheritingLabelAttributesAndConfiguringWithBlock:nil]; - return; - } - - self.attributedText = text; - self.activeLink = nil; - - self.linkModels = [NSArray array]; - if (text && self.attributedText && self.enabledTextCheckingTypes) { - __weak __typeof(self)weakSelf = self; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - __strong __typeof(weakSelf)strongSelf = weakSelf; - - NSDataDetector *dataDetector = strongSelf.dataDetector; - if (dataDetector && [dataDetector respondsToSelector:@selector(matchesInString:options:range:)]) { - NSArray *results = [dataDetector matchesInString:[(NSAttributedString *)text string] options:0 range:NSMakeRange(0, [(NSAttributedString *)text length])]; - if ([results count] > 0) { - dispatch_async(dispatch_get_main_queue(), ^{ - if ([[strongSelf.attributedText string] isEqualToString:[(NSAttributedString *)text string]]) { - [strongSelf addLinksWithTextCheckingResults:results attributes:strongSelf.linkAttributes]; - } - }); - } - } - }); - } - - [self.attributedText enumerateAttribute:NSLinkAttributeName inRange:NSMakeRange(0, self.attributedText.length) options:0 usingBlock:^(id value, NSRange range, __unused BOOL *stop) { - if (value) { - NSURL *URL = [value isKindOfClass:[NSString class]] ? [NSURL URLWithString:value] : value; - [self addLinkToURL:URL withRange:range]; - } - }]; -} - -- (void)setText:(id)text -afterInheritingLabelAttributesAndConfiguringWithBlock:(NSMutableAttributedString * (^)(NSMutableAttributedString *mutableAttributedString))block -{ - NSMutableAttributedString *mutableAttributedString = nil; - if ([text isKindOfClass:[NSString class]]) { - mutableAttributedString = [[NSMutableAttributedString alloc] initWithString:text attributes:NSAttributedStringAttributesFromLabel(self)]; - } else { - mutableAttributedString = [[NSMutableAttributedString alloc] initWithAttributedString:text]; - [mutableAttributedString addAttributes:NSAttributedStringAttributesFromLabel(self) range:NSMakeRange(0, [mutableAttributedString length])]; - } - - if (block) { - mutableAttributedString = block(mutableAttributedString); - } - - [self setText:mutableAttributedString]; -} - -- (void)setActiveLink:(BITAttributedLabelLink *)activeLink { - _activeLink = activeLink; - - NSDictionary *activeAttributes = activeLink.activeAttributes ?: self.activeLinkAttributes; - - if (_activeLink && activeAttributes.count > 0) { - if (!self.inactiveAttributedText) { - self.inactiveAttributedText = [self.attributedText copy]; - } - - NSMutableAttributedString *mutableAttributedString = [self.inactiveAttributedText mutableCopy]; - if (self.activeLink.result.range.length > 0 && NSLocationInRange(NSMaxRange(self.activeLink.result.range) - 1, NSMakeRange(0, [self.inactiveAttributedText length]))) { - [mutableAttributedString addAttributes:activeAttributes range:self.activeLink.result.range]; - } - - self.attributedText = mutableAttributedString; - [self setNeedsDisplay]; - - [CATransaction flush]; - } else if (self.inactiveAttributedText) { - self.attributedText = self.inactiveAttributedText; - self.inactiveAttributedText = nil; - - [self setNeedsDisplay]; - } -} - -- (void)setLinkAttributes:(NSDictionary *)linkAttributes { - _linkAttributes = convertNSAttributedStringAttributesToCTAttributes(linkAttributes); -} - -- (void)setActiveLinkAttributes:(NSDictionary *)activeLinkAttributes { - _activeLinkAttributes = convertNSAttributedStringAttributesToCTAttributes(activeLinkAttributes); -} - -- (void)setInactiveLinkAttributes:(NSDictionary *)inactiveLinkAttributes { - _inactiveLinkAttributes = convertNSAttributedStringAttributesToCTAttributes(inactiveLinkAttributes); -} - -#pragma mark - UILabel - -- (void)setHighlighted:(BOOL)highlighted { - [super setHighlighted:highlighted]; - [self setNeedsDisplay]; -} - -// Fixes crash when loading from a UIStoryboard -- (UIColor *)textColor { - UIColor *color = [super textColor]; - if (!color) { - color = [UIColor blackColor]; - } - - return color; -} - -- (void)setTextColor:(UIColor *)textColor { - UIColor *oldTextColor = self.textColor; - [super setTextColor:textColor]; - - // Redraw to allow any ColorFromContext attributes a chance to update - if (textColor != oldTextColor) { - [self setNeedsFramesetter]; - [self setNeedsDisplay]; - } -} - -- (CGRect)textRectForBounds:(CGRect)bounds - limitedToNumberOfLines:(NSInteger)numberOfLines -{ - bounds = UIEdgeInsetsInsetRect(bounds, self.textInsets); - if (!self.attributedText) { - return [super textRectForBounds:bounds limitedToNumberOfLines:numberOfLines]; - } - - CGRect textRect = bounds; - - // Calculate height with a minimum of double the font pointSize, to ensure that CTFramesetterSuggestFrameSizeWithConstraints doesn't return CGSizeZero, as it would if textRect height is insufficient. - textRect.size.height = MAX(self.font.lineHeight * MAX(2, numberOfLines), bounds.size.height); - - // Adjust the text to be in the center vertically, if the text size is smaller than bounds - CGSize textSize = CTFramesetterSuggestFrameSizeWithConstraints([self framesetter], CFRangeMake(0, (CFIndex)[self.attributedText length]), NULL, textRect.size, NULL); - textSize = CGSizeMake(CGFloat_ceil(textSize.width), CGFloat_ceil(textSize.height)); // Fix for iOS 4, CTFramesetterSuggestFrameSizeWithConstraints sometimes returns fractional sizes - - if (textSize.height < bounds.size.height) { - CGFloat yOffset = 0.0f; - switch (self.verticalAlignment) { - case BITAttributedLabelVerticalAlignmentCenter: - yOffset = CGFloat_floor((bounds.size.height - textSize.height) / 2.0f); - break; - case BITAttributedLabelVerticalAlignmentBottom: - yOffset = bounds.size.height - textSize.height; - break; - case BITAttributedLabelVerticalAlignmentTop: - default: - break; - } - - textRect.origin.y += yOffset; - } - - return textRect; -} - -- (void)drawTextInRect:(CGRect)rect { - CGRect insetRect = UIEdgeInsetsInsetRect(rect, self.textInsets); - if (!self.attributedText) { - [super drawTextInRect:insetRect]; - return; - } - - NSAttributedString *originalAttributedText = nil; - - // Adjust the font size to fit width, if necessarry - if (self.adjustsFontSizeToFitWidth && self.numberOfLines > 0) { - // Framesetter could still be working with a resized version of the text; - // need to reset so we start from the original font size. - // See #393. - [self setNeedsFramesetter]; - [self setNeedsDisplay]; - - if ([self respondsToSelector:@selector(invalidateIntrinsicContentSize)]) { - [self invalidateIntrinsicContentSize]; - } - - // Use infinite width to find the max width, which will be compared to availableWidth if needed. - CGSize maxSize = (self.numberOfLines > 1) ? CGSizeMake(BITFLOAT_MAX, BITFLOAT_MAX) : CGSizeZero; - - CGFloat textWidth = [self sizeThatFits:maxSize].width; - CGFloat availableWidth = self.frame.size.width * self.numberOfLines; - if (self.numberOfLines > 1 && self.lineBreakMode == BITLineBreakByWordWrapping) { - textWidth *= kBITLineBreakWordWrapTextWidthScalingFactor; - } - - if (textWidth > availableWidth && textWidth > 0.0f) { - originalAttributedText = [self.attributedText copy]; - - CGFloat scaleFactor = availableWidth / textWidth; - if ([self respondsToSelector:@selector(minimumScaleFactor)] && self.minimumScaleFactor > scaleFactor) { - scaleFactor = self.minimumScaleFactor; - } - - self.attributedText = NSAttributedStringByScalingFontSize(self.attributedText, scaleFactor); - } - } - - CGContextRef c = UIGraphicsGetCurrentContext(); - CGContextSaveGState(c); - { - CGContextSetTextMatrix(c, CGAffineTransformIdentity); - - // Inverts the CTM to match iOS coordinates (otherwise text draws upside-down; Mac OS's system is different) - CGContextTranslateCTM(c, 0.0f, insetRect.size.height); - CGContextScaleCTM(c, 1.0f, -1.0f); - - CFRange textRange = CFRangeMake(0, (CFIndex)[self.attributedText length]); - - // First, get the text rect (which takes vertical centering into account) - CGRect textRect = [self textRectForBounds:rect limitedToNumberOfLines:self.numberOfLines]; - - // CoreText draws its text aligned to the bottom, so we move the CTM here to take our vertical offsets into account - CGContextTranslateCTM(c, insetRect.origin.x, insetRect.size.height - textRect.origin.y - textRect.size.height); - - // Second, trace the shadow before the actual text, if we have one - if (self.shadowColor && !self.highlighted) { - CGContextSetShadowWithColor(c, self.shadowOffset, self.shadowRadius, [self.shadowColor CGColor]); - } else if (self.highlightedShadowColor) { - CGContextSetShadowWithColor(c, self.highlightedShadowOffset, self.highlightedShadowRadius, [self.highlightedShadowColor CGColor]); - } - - // Finally, draw the text or highlighted text itself (on top of the shadow, if there is one) - if (self.highlightedTextColor && self.highlighted) { - NSMutableAttributedString *highlightAttributedString = [self.renderedAttributedText mutableCopy]; - [highlightAttributedString addAttribute:(__bridge NSString *)kCTForegroundColorAttributeName value:(id)[self.highlightedTextColor CGColor] range:NSMakeRange(0, highlightAttributedString.length)]; - - if (![self highlightFramesetter]) { - CTFramesetterRef highlightFramesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)highlightAttributedString); - [self setHighlightFramesetter:highlightFramesetter]; - CFRelease(highlightFramesetter); - } - - [self drawFramesetter:[self highlightFramesetter] attributedString:highlightAttributedString textRange:textRange inRect:textRect context:c]; - } else { - [self drawFramesetter:[self framesetter] attributedString:self.renderedAttributedText textRange:textRange inRect:textRect context:c]; - } - - // If we adjusted the font size, set it back to its original size - if (originalAttributedText) { - // Use ivar directly to avoid clearing out framesetter and renderedAttributedText - _attributedText = originalAttributedText; - } - } - CGContextRestoreGState(c); -} - -#pragma mark - UIAccessibilityElement - -- (BOOL)isAccessibilityElement { - return NO; -} - -- (NSInteger)accessibilityElementCount { - return (NSInteger)[[self accessibilityElements] count]; -} - -- (id)accessibilityElementAtIndex:(NSInteger)index { - return [[self accessibilityElements] objectAtIndex:(NSUInteger)index]; -} - -- (NSInteger)indexOfAccessibilityElement:(id)element { - return (NSInteger)[[self accessibilityElements] indexOfObject:element]; -} - -- (NSArray *)accessibilityElements { - if (!_accessibilityElements) { - @synchronized(self) { - NSMutableArray *mutableAccessibilityItems = [NSMutableArray array]; - - for (BITAttributedLabelLink *link in self.linkModels) { - - if (link.result.range.location == NSNotFound) { - continue; - } - - NSString *sourceText = [self.text isKindOfClass:[NSString class]] ? self.text : [(NSAttributedString *)self.text string]; - - NSString *accessibilityLabel = [sourceText substringWithRange:link.result.range]; - NSString *accessibilityValue = link.accessibilityValue; - - if (accessibilityLabel) { - BITAccessibilityElement *linkElement = [[BITAccessibilityElement alloc] initWithAccessibilityContainer:self]; - linkElement.accessibilityTraits = UIAccessibilityTraitLink; - linkElement.boundingRect = [self boundingRectForCharacterRange:link.result.range]; - linkElement.superview = self; - linkElement.accessibilityLabel = accessibilityLabel; - - if (![accessibilityLabel isEqualToString:accessibilityValue]) { - linkElement.accessibilityValue = accessibilityValue; - } - - [mutableAccessibilityItems addObject:linkElement]; - } - } - - BITAccessibilityElement *baseElement = [[BITAccessibilityElement alloc] initWithAccessibilityContainer:self]; - baseElement.accessibilityLabel = [super accessibilityLabel]; - baseElement.accessibilityHint = [super accessibilityHint]; - baseElement.accessibilityValue = [super accessibilityValue]; - baseElement.boundingRect = self.bounds; - baseElement.superview = self; - baseElement.accessibilityTraits = [super accessibilityTraits]; - - [mutableAccessibilityItems addObject:baseElement]; - - self.accessibilityElements = [NSArray arrayWithArray:mutableAccessibilityItems]; - } - } - - return _accessibilityElements; -} - -#pragma mark - UIView - -- (CGSize)sizeThatFits:(CGSize)size { - if (!self.attributedText) { - return [super sizeThatFits:size]; - } else { - NSAttributedString *string = [self renderedAttributedText]; - - CGSize labelSize = CTFramesetterSuggestFrameSizeForAttributedStringWithConstraints([self framesetter], string, size, (NSUInteger)self.numberOfLines); - labelSize.width += self.textInsets.left + self.textInsets.right; - labelSize.height += self.textInsets.top + self.textInsets.bottom; - - return labelSize; - } -} - -- (CGSize)intrinsicContentSize { - // There's an implicit width from the original UILabel implementation - return [self sizeThatFits:[super intrinsicContentSize]]; -} - -- (void)tintColorDidChange { - if (!self.inactiveLinkAttributes || [self.inactiveLinkAttributes count] == 0) { - return; - } - - BOOL isInactive = (self.tintAdjustmentMode == UIViewTintAdjustmentModeDimmed); - - NSMutableAttributedString *mutableAttributedString = [self.attributedText mutableCopy]; - for (BITAttributedLabelLink *link in self.linkModels) { - NSDictionary *attributesToRemove = isInactive ? link.attributes : link.inactiveAttributes; - NSDictionary *attributesToAdd = isInactive ? link.inactiveAttributes : link.attributes; - - [attributesToRemove enumerateKeysAndObjectsUsingBlock:^(NSString *name, __unused id value, __unused BOOL *stop) { - if (NSMaxRange(link.result.range) <= mutableAttributedString.length) { - [mutableAttributedString removeAttribute:name range:link.result.range]; - } - }]; - - if (attributesToAdd) { - if (NSMaxRange(link.result.range) <= mutableAttributedString.length) { - [mutableAttributedString addAttributes:attributesToAdd range:link.result.range]; - } - } - } - - self.attributedText = mutableAttributedString; - - [self setNeedsDisplay]; -} - -- (UIView *)hitTest:(CGPoint)point - withEvent:(UIEvent *)event -{ - if (![self linkAtPoint:point] || !self.userInteractionEnabled || self.hidden || self.alpha < 0.01) { - return [super hitTest:point withEvent:event]; - } - - return self; -} - -#pragma mark - UIResponder - -- (BOOL)canBecomeFirstResponder { - return YES; -} - -- (BOOL)canPerformAction:(SEL)action - withSender:(__unused id)sender -{ -#if !TARGET_OS_TV - return (action == @selector(copy:)); -#else - return NO; -#endif -} - -- (void)touchesBegan:(NSSet *)touches - withEvent:(UIEvent *)event -{ - UITouch *touch = [touches anyObject]; - - self.activeLink = [self linkAtPoint:[touch locationInView:self]]; - - if (!self.activeLink) { - [super touchesBegan:touches withEvent:event]; - } -} - -- (void)touchesMoved:(NSSet *)touches - withEvent:(UIEvent *)event -{ - if (self.activeLink) { - UITouch *touch = [touches anyObject]; - - if (self.activeLink != [self linkAtPoint:[touch locationInView:self]]) { - self.activeLink = nil; - } - } else { - [super touchesMoved:touches withEvent:event]; - } -} - -- (void)touchesEnded:(NSSet *)touches - withEvent:(UIEvent *)event -{ - if (self.activeLink) { - if (self.activeLink.linkTapBlock) { - self.activeLink.linkTapBlock(self, self.activeLink); - self.activeLink = nil; - return; - } - - NSTextCheckingResult *result = self.activeLink.result; - self.activeLink = nil; - - switch (result.resultType) { - case NSTextCheckingTypeLink: - if ([self.delegate respondsToSelector:@selector(attributedLabel:didSelectLinkWithURL:)]) { - [self.delegate attributedLabel:self didSelectLinkWithURL:result.URL]; - return; - } - break; - case NSTextCheckingTypeAddress: - if ([self.delegate respondsToSelector:@selector(attributedLabel:didSelectLinkWithAddress:)]) { - [self.delegate attributedLabel:self didSelectLinkWithAddress:result.addressComponents]; - return; - } - break; - case NSTextCheckingTypePhoneNumber: - if ([self.delegate respondsToSelector:@selector(attributedLabel:didSelectLinkWithPhoneNumber:)]) { - [self.delegate attributedLabel:self didSelectLinkWithPhoneNumber:result.phoneNumber]; - return; - } - break; - case NSTextCheckingTypeDate: - if (result.timeZone && [self.delegate respondsToSelector:@selector(attributedLabel:didSelectLinkWithDate:timeZone:duration:)]) { - [self.delegate attributedLabel:self didSelectLinkWithDate:result.date timeZone:result.timeZone duration:result.duration]; - return; - } else if ([self.delegate respondsToSelector:@selector(attributedLabel:didSelectLinkWithDate:)]) { - [self.delegate attributedLabel:self didSelectLinkWithDate:result.date]; - return; - } - break; - case NSTextCheckingTypeTransitInformation: - if ([self.delegate respondsToSelector:@selector(attributedLabel:didSelectLinkWithTransitInformation:)]) { - [self.delegate attributedLabel:self didSelectLinkWithTransitInformation:result.components]; - return; - } - default: - break; - } - - // Fallback to `attributedLabel:didSelectLinkWithTextCheckingResult:` if no other delegate method matched. - if ([self.delegate respondsToSelector:@selector(attributedLabel:didSelectLinkWithTextCheckingResult:)]) { - [self.delegate attributedLabel:self didSelectLinkWithTextCheckingResult:result]; - } - } else { - [super touchesEnded:touches withEvent:event]; - } -} - -- (void)touchesCancelled:(NSSet *)touches - withEvent:(UIEvent *)event -{ - if (self.activeLink) { - self.activeLink = nil; - } else { - [super touchesCancelled:touches withEvent:event]; - } -} - -#pragma mark - UIGestureRecognizerDelegate - -- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { - return [self containslinkAtPoint:[touch locationInView:self]]; -} - -#pragma mark - UILongPressGestureRecognizer - -- (void)longPressGestureDidFire:(UILongPressGestureRecognizer *)sender { - switch (sender.state) { - case UIGestureRecognizerStateBegan: { - CGPoint touchPoint = [sender locationInView:self]; - BITAttributedLabelLink *link = [self linkAtPoint:touchPoint]; - - if (link) { - if (link.linkLongPressBlock) { - link.linkLongPressBlock(self, link); - return; - } - - NSTextCheckingResult *result = link.result; - - if (!result) { - return; - } - - switch (result.resultType) { - case NSTextCheckingTypeLink: - if ([self.delegate respondsToSelector:@selector(attributedLabel:didLongPressLinkWithURL:atPoint:)]) { - [self.delegate attributedLabel:self didLongPressLinkWithURL:result.URL atPoint:touchPoint]; - return; - } - break; - case NSTextCheckingTypeAddress: - if ([self.delegate respondsToSelector:@selector(attributedLabel:didLongPressLinkWithAddress:atPoint:)]) { - [self.delegate attributedLabel:self didLongPressLinkWithAddress:result.addressComponents atPoint:touchPoint]; - return; - } - break; - case NSTextCheckingTypePhoneNumber: - if ([self.delegate respondsToSelector:@selector(attributedLabel:didLongPressLinkWithPhoneNumber:atPoint:)]) { - [self.delegate attributedLabel:self didLongPressLinkWithPhoneNumber:result.phoneNumber atPoint:touchPoint]; - return; - } - break; - case NSTextCheckingTypeDate: - if (result.timeZone && [self.delegate respondsToSelector:@selector(attributedLabel:didLongPressLinkWithDate:timeZone:duration:atPoint:)]) { - [self.delegate attributedLabel:self didLongPressLinkWithDate:result.date timeZone:result.timeZone duration:result.duration atPoint:touchPoint]; - return; - } else if ([self.delegate respondsToSelector:@selector(attributedLabel:didLongPressLinkWithDate:atPoint:)]) { - [self.delegate attributedLabel:self didLongPressLinkWithDate:result.date atPoint:touchPoint]; - return; - } - break; - case NSTextCheckingTypeTransitInformation: - if ([self.delegate respondsToSelector:@selector(attributedLabel:didLongPressLinkWithTransitInformation:atPoint:)]) { - [self.delegate attributedLabel:self didLongPressLinkWithTransitInformation:result.components atPoint:touchPoint]; - return; - } - default: - break; - } - - // Fallback to `attributedLabel:didLongPressLinkWithTextCheckingResult:atPoint:` if no other delegate method matched. - if ([self.delegate respondsToSelector:@selector(attributedLabel:didLongPressLinkWithTextCheckingResult:atPoint:)]) { - [self.delegate attributedLabel:self didLongPressLinkWithTextCheckingResult:result atPoint:touchPoint]; - } - } - break; - } - default: - break; - } -} - -#if !TARGET_OS_TV -#pragma mark - UIResponderStandardEditActions - -- (void)copy:(__unused id)sender { - [[UIPasteboard generalPasteboard] setString:self.text]; -} -#endif - -#pragma mark - NSCoding - -- (void)encodeWithCoder:(NSCoder *)coder { - [super encodeWithCoder:coder]; - - [coder encodeObject:@(self.enabledTextCheckingTypes) forKey:NSStringFromSelector(@selector(enabledTextCheckingTypes))]; - - [coder encodeObject:self.linkModels forKey:NSStringFromSelector(@selector(linkModels))]; - if ([NSMutableParagraphStyle class]) { - [coder encodeObject:self.linkAttributes forKey:NSStringFromSelector(@selector(linkAttributes))]; - [coder encodeObject:self.activeLinkAttributes forKey:NSStringFromSelector(@selector(activeLinkAttributes))]; - [coder encodeObject:self.inactiveLinkAttributes forKey:NSStringFromSelector(@selector(inactiveLinkAttributes))]; - } - [coder encodeObject:@(self.shadowRadius) forKey:NSStringFromSelector(@selector(shadowRadius))]; - [coder encodeObject:@(self.highlightedShadowRadius) forKey:NSStringFromSelector(@selector(highlightedShadowRadius))]; - [coder encodeCGSize:self.highlightedShadowOffset forKey:NSStringFromSelector(@selector(highlightedShadowOffset))]; - [coder encodeObject:self.highlightedShadowColor forKey:NSStringFromSelector(@selector(highlightedShadowColor))]; - [coder encodeObject:@(self.kern) forKey:NSStringFromSelector(@selector(kern))]; - [coder encodeObject:@(self.firstLineIndent) forKey:NSStringFromSelector(@selector(firstLineIndent))]; - [coder encodeObject:@(self.lineSpacing) forKey:NSStringFromSelector(@selector(lineSpacing))]; - [coder encodeObject:@(self.lineHeightMultiple) forKey:NSStringFromSelector(@selector(lineHeightMultiple))]; - [coder encodeUIEdgeInsets:self.textInsets forKey:NSStringFromSelector(@selector(textInsets))]; - [coder encodeInteger:self.verticalAlignment forKey:NSStringFromSelector(@selector(verticalAlignment))]; - - [coder encodeObject:self.attributedTruncationToken forKey:NSStringFromSelector(@selector(attributedTruncationToken))]; - - [coder encodeObject:NSStringFromUIEdgeInsets(self.linkBackgroundEdgeInset) forKey:NSStringFromSelector(@selector(linkBackgroundEdgeInset))]; - [coder encodeObject:self.attributedText forKey:NSStringFromSelector(@selector(attributedText))]; - [coder encodeObject:self.text forKey:NSStringFromSelector(@selector(text))]; -} - -- (id)initWithCoder:(NSCoder *)coder { - self = [super initWithCoder:coder]; - if (!self) { - return nil; - } - - [self commonInit]; - - if ([coder containsValueForKey:NSStringFromSelector(@selector(enabledTextCheckingTypes))]) { - self.enabledTextCheckingTypes = [[coder decodeObjectForKey:NSStringFromSelector(@selector(enabledTextCheckingTypes))] unsignedLongLongValue]; - } - - if ([NSMutableParagraphStyle class]) { - if ([coder containsValueForKey:NSStringFromSelector(@selector(linkAttributes))]) { - self.linkAttributes = [coder decodeObjectForKey:NSStringFromSelector(@selector(linkAttributes))]; - } - - if ([coder containsValueForKey:NSStringFromSelector(@selector(activeLinkAttributes))]) { - self.activeLinkAttributes = [coder decodeObjectForKey:NSStringFromSelector(@selector(activeLinkAttributes))]; - } - - if ([coder containsValueForKey:NSStringFromSelector(@selector(inactiveLinkAttributes))]) { - self.inactiveLinkAttributes = [coder decodeObjectForKey:NSStringFromSelector(@selector(inactiveLinkAttributes))]; - } - } - - if ([coder containsValueForKey:NSStringFromSelector(@selector(links))]) { - NSArray *oldLinks = [coder decodeObjectForKey:NSStringFromSelector(@selector(links))]; - [self addLinksWithTextCheckingResults:oldLinks attributes:nil]; - } - - if ([coder containsValueForKey:NSStringFromSelector(@selector(linkModels))]) { - self.linkModels = [coder decodeObjectForKey:NSStringFromSelector(@selector(linkModels))]; - } - - if ([coder containsValueForKey:NSStringFromSelector(@selector(shadowRadius))]) { - self.shadowRadius = [[coder decodeObjectForKey:NSStringFromSelector(@selector(shadowRadius))] floatValue]; - } - - if ([coder containsValueForKey:NSStringFromSelector(@selector(highlightedShadowRadius))]) { - self.highlightedShadowRadius = [[coder decodeObjectForKey:NSStringFromSelector(@selector(highlightedShadowRadius))] floatValue]; - } - - if ([coder containsValueForKey:NSStringFromSelector(@selector(highlightedShadowOffset))]) { - self.highlightedShadowOffset = [coder decodeCGSizeForKey:NSStringFromSelector(@selector(highlightedShadowOffset))]; - } - - if ([coder containsValueForKey:NSStringFromSelector(@selector(highlightedShadowColor))]) { - self.highlightedShadowColor = [coder decodeObjectForKey:NSStringFromSelector(@selector(highlightedShadowColor))]; - } - - if ([coder containsValueForKey:NSStringFromSelector(@selector(kern))]) { - self.kern = [[coder decodeObjectForKey:NSStringFromSelector(@selector(kern))] floatValue]; - } - - if ([coder containsValueForKey:NSStringFromSelector(@selector(firstLineIndent))]) { - self.firstLineIndent = [[coder decodeObjectForKey:NSStringFromSelector(@selector(firstLineIndent))] floatValue]; - } - - if ([coder containsValueForKey:NSStringFromSelector(@selector(lineSpacing))]) { - self.lineSpacing = [[coder decodeObjectForKey:NSStringFromSelector(@selector(lineSpacing))] floatValue]; - } - - if ([coder containsValueForKey:NSStringFromSelector(@selector(minimumLineHeight))]) { - self.minimumLineHeight = [[coder decodeObjectForKey:NSStringFromSelector(@selector(minimumLineHeight))] floatValue]; - } - - if ([coder containsValueForKey:NSStringFromSelector(@selector(maximumLineHeight))]) { - self.maximumLineHeight = [[coder decodeObjectForKey:NSStringFromSelector(@selector(maximumLineHeight))] floatValue]; - } - - if ([coder containsValueForKey:NSStringFromSelector(@selector(lineHeightMultiple))]) { - self.lineHeightMultiple = [[coder decodeObjectForKey:NSStringFromSelector(@selector(lineHeightMultiple))] floatValue]; - } - - if ([coder containsValueForKey:NSStringFromSelector(@selector(textInsets))]) { - self.textInsets = [coder decodeUIEdgeInsetsForKey:NSStringFromSelector(@selector(textInsets))]; - } - - if ([coder containsValueForKey:NSStringFromSelector(@selector(verticalAlignment))]) { - self.verticalAlignment = [coder decodeIntegerForKey:NSStringFromSelector(@selector(verticalAlignment))]; - } - - if ([coder containsValueForKey:NSStringFromSelector(@selector(attributedTruncationToken))]) { - self.attributedTruncationToken = [coder decodeObjectForKey:NSStringFromSelector(@selector(attributedTruncationToken))]; - } - - if ([coder containsValueForKey:NSStringFromSelector(@selector(linkBackgroundEdgeInset))]) { - self.linkBackgroundEdgeInset = UIEdgeInsetsFromString([coder decodeObjectForKey:NSStringFromSelector(@selector(linkBackgroundEdgeInset))]); - } - - if ([coder containsValueForKey:NSStringFromSelector(@selector(attributedText))]) { - self.attributedText = [coder decodeObjectForKey:NSStringFromSelector(@selector(attributedText))]; - } else { - self.text = super.text; - } - - return self; -} - -@end - -#pragma mark - BITAttributedLabelLink - -@implementation BITAttributedLabelLink - -- (instancetype)initWithAttributes:(NSDictionary *)attributes - activeAttributes:(NSDictionary *)activeAttributes - inactiveAttributes:(NSDictionary *)inactiveAttributes - textCheckingResult:(NSTextCheckingResult *)result { - - if ((self = [super init])) { - _result = result; - _attributes = [attributes copy]; - _activeAttributes = [activeAttributes copy]; - _inactiveAttributes = [inactiveAttributes copy]; - } - - return self; -} - -- (instancetype)initWithAttributesFromLabel:(BITAttributedLabel*)label - textCheckingResult:(NSTextCheckingResult *)result { - - return [self initWithAttributes:label.linkAttributes - activeAttributes:label.activeLinkAttributes - inactiveAttributes:label.inactiveLinkAttributes - textCheckingResult:result]; -} - -#pragma mark - Accessibility - -- (NSString *) accessibilityValue { - if ([_accessibilityValue length] == 0) { - switch (self.result.resultType) { - case NSTextCheckingTypeLink: - _accessibilityValue = self.result.URL.absoluteString; - break; - case NSTextCheckingTypePhoneNumber: - _accessibilityValue = self.result.phoneNumber; - break; - case NSTextCheckingTypeDate: - _accessibilityValue = [NSDateFormatter localizedStringFromDate:self.result.date - dateStyle:NSDateFormatterLongStyle - timeStyle:NSDateFormatterLongStyle]; - break; - default: - break; - } - } - - return _accessibilityValue; -} - -#pragma mark - NSCoding - -- (void)encodeWithCoder:(NSCoder *)aCoder { - [aCoder encodeObject:self.result forKey:NSStringFromSelector(@selector(result))]; - [aCoder encodeObject:self.attributes forKey:NSStringFromSelector(@selector(attributes))]; - [aCoder encodeObject:self.activeAttributes forKey:NSStringFromSelector(@selector(activeAttributes))]; - [aCoder encodeObject:self.inactiveAttributes forKey:NSStringFromSelector(@selector(inactiveAttributes))]; - [aCoder encodeObject:self.accessibilityValue forKey:NSStringFromSelector(@selector(accessibilityValue))]; -} - -- (id)initWithCoder:(NSCoder *)aDecoder { - if ((self = [super init])) { - _result = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(result))]; - _attributes = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(attributes))]; - _activeAttributes = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(activeAttributes))]; - _inactiveAttributes = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(inactiveAttributes))]; - self.accessibilityValue = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(accessibilityValue))]; - } - - return self; -} - -@end - -#pragma mark - - -static inline CGColorRef CGColorRefFromColor(id color) { - return [color isKindOfClass:[UIColor class]] ? [color CGColor] : (__bridge CGColorRef)color; -} - -static inline CTFontRef CTFontRefFromUIFont(UIFont * font) { - CTFontRef ctfont = CTFontCreateWithName((__bridge CFStringRef)font.fontName, font.pointSize, NULL); - return CFAutorelease(ctfont); -} - -static inline NSDictionary * convertNSAttributedStringAttributesToCTAttributes(NSDictionary *attributes) { - if (!attributes) return nil; - - NSMutableDictionary *mutableAttributes = [NSMutableDictionary dictionary]; - - NSDictionary *NSToCTAttributeNamesMap = @{ - NSFontAttributeName: (NSString *)kCTFontAttributeName, - NSBackgroundColorAttributeName: (NSString *)kBITBackgroundFillColorAttributeName, - NSForegroundColorAttributeName: (NSString *)kCTForegroundColorAttributeName, - NSUnderlineColorAttributeName: (NSString *)kCTUnderlineColorAttributeName, - NSUnderlineStyleAttributeName: (NSString *)kCTUnderlineStyleAttributeName, - NSStrokeWidthAttributeName: (NSString *)kCTStrokeWidthAttributeName, - NSStrokeColorAttributeName: (NSString *)kCTStrokeWidthAttributeName, - NSKernAttributeName: (NSString *)kCTKernAttributeName, - NSLigatureAttributeName: (NSString *)kCTLigatureAttributeName - }; - - [attributes enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) { - key = [NSToCTAttributeNamesMap objectForKey:key] ? : key; - - if (![NSMutableParagraphStyle class]) { - if ([value isKindOfClass:[UIFont class]]) { - value = (__bridge id)CTFontRefFromUIFont(value); - } else if ([value isKindOfClass:[UIColor class]]) { - value = (__bridge id)((UIColor *)value).CGColor; - } - } - - [mutableAttributes setObject:value forKey:key]; - }]; - - return [NSDictionary dictionaryWithDictionary:mutableAttributes]; -} - -#pragma clang diagnostic pop - -#endif diff --git a/submodules/HockeySDK-iOS/Classes/BITAuthenticationViewController.h b/submodules/HockeySDK-iOS/Classes/BITAuthenticationViewController.h deleted file mode 100644 index 2eac237f18..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITAuthenticationViewController.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Author: Stephan Diederich - * - * Copyright (c) 2013-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import -@protocol BITAuthenticationViewControllerDelegate; -@class BITAuthenticator; -@class BITHockeyAppClient; - -/** - * View controller handling user interaction for `BITAuthenticator` - */ -@interface BITAuthenticationViewController : UITableViewController - -- (instancetype) initWithDelegate:(id) delegate; - -/** - * can be set to YES to show an additional button + description text - * and allowing to login via external website/UDID. - * if this is set to yes, no further email/password options are shown - * - * defaults to NO - */ -@property (nonatomic, assign) BOOL showsLoginViaWebButton; - -/** - * Description shown on top of view. Should tell why this view - * was presented and what's next. - */ -@property (nonatomic, copy) NSString* tableViewTitle; - -/** - * can be set to YES to also require the users password - * - * defaults to NO - */ -@property (nonatomic, assign) BOOL requirePassword; - -@property (nonatomic, weak) id delegate; - -/** - * allows to pre-fill the email-addy - */ -@property (nonatomic, copy) NSString* email; -@end - -/** - * BITAuthenticationViewController protocol - */ -@protocol BITAuthenticationViewControllerDelegate - -- (void) authenticationViewControllerDidTapWebButton:(UIViewController*) viewController; - -/** - * called when the user wants to login - * - * @param viewController the delegating view controller - * @param email the content of the email-field - * @param password the content of the password-field (if existent) - * @param completion Must be called by the delegate once the auth-task completed - * This view controller shows an activity-indicator in between and blocks - * the UI. if succeeded is NO, it shows an alertView presenting the error - * given by the completion block - */ -- (void) authenticationViewController:(UIViewController*) viewController - handleAuthenticationWithEmail:(NSString*) email - password:(NSString*) password - completion:(void(^)(BOOL succeeded, NSError *error)) completion; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITAuthenticationViewController.m b/submodules/HockeySDK-iOS/Classes/BITAuthenticationViewController.m deleted file mode 100644 index bcf765683a..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITAuthenticationViewController.m +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Author: Stephan Diederich - * - * Copyright (c) 2013-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_AUTHENTICATOR - -#import "BITAuthenticationViewController.h" -#import "BITAuthenticator_Private.h" -#import "HockeySDKPrivate.h" -#import "BITHockeyHelper.h" -#import "BITHockeyAppClient.h" -#import - -@interface BITAuthenticationViewController () - -@property (nonatomic, weak) UITextField *emailField; -@property (nonatomic, copy) NSString *password; - -@end - -@implementation BITAuthenticationViewController - -- (instancetype) initWithDelegate:(id)delegate { - self = [super initWithStyle:UITableViewStyleGrouped]; - if (self) { - self.title = BITHockeyLocalizedString(@"HockeyAuthenticatorViewControllerTitle"); - _delegate = delegate; - } - return self; -} - -#pragma mark - view lifecycle - -- (void)viewDidLoad { - [super viewDidLoad]; - - [self.tableView setScrollEnabled:NO]; - - [self updateWebLoginButton]; -} - -- (void)viewWillAppear:(BOOL)animated { - [super viewWillAppear:animated]; - - [self updateBarButtons]; - - self.navigationItem.rightBarButtonItem.enabled = [self allRequiredFieldsEntered]; -} - -#pragma mark - Property overrides - -- (void) updateBarButtons { - if(self.showsLoginViaWebButton) { - self.navigationItem.rightBarButtonItem = nil; - } else { - self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone - target:self - action:@selector(saveAction:)]; - } -} - -- (void)setShowsLoginViaWebButton:(BOOL)showsLoginViaWebButton { - if(_showsLoginViaWebButton != showsLoginViaWebButton) { - _showsLoginViaWebButton = showsLoginViaWebButton; - if(self.isViewLoaded) { - [self.tableView reloadData]; - [self updateBarButtons]; - [self updateWebLoginButton]; - } - } -} - -- (void) updateWebLoginButton { - if(self.showsLoginViaWebButton) { - static const CGFloat kFooterHeight = 60.0; - UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, - CGRectGetWidth(self.tableView.bounds), - kFooterHeight)]; - UIButton *button = [UIButton buttonWithType:kBITButtonTypeSystem]; - [button setTitle:BITHockeyLocalizedString(@"HockeyAuthenticationViewControllerWebLoginButtonTitle") forState:UIControlStateNormal]; - CGSize buttonSize = [button sizeThatFits:CGSizeMake(CGRectGetWidth(self.tableView.bounds), - kFooterHeight)]; - button.frame = CGRectMake(floor((CGRectGetWidth(containerView.bounds) - buttonSize.width) / (CGFloat)2.0), - floor((kFooterHeight - buttonSize.height) / (CGFloat)2.0), - buttonSize.width, - buttonSize.height); - button.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; - if ([UIButton instancesRespondToSelector:(NSSelectorFromString(@"setTintColor:"))]) { - [button setTitleColor:BIT_RGBCOLOR(0, 122, 255) forState:UIControlStateNormal]; - } - [containerView addSubview:button]; - [button addTarget:self - action:@selector(handleWebLoginButton:) - forControlEvents:UIControlEventTouchUpInside]; - self.tableView.tableFooterView = containerView; - } else { - self.tableView.tableFooterView = nil; - } -} - -- (IBAction) handleWebLoginButton:(id) __unused sender { - [self.delegate authenticationViewControllerDidTapWebButton:self]; -} - -- (void)setEmail:(NSString *)email { - _email = email; - if(self.isViewLoaded) { - self.emailField.text = email; - } -} - -- (void)setTableViewTitle:(NSString *)viewDescription { - _tableViewTitle = [viewDescription copy]; - if(self.isViewLoaded) { - [self.tableView reloadData]; - } -} -#pragma mark - UIViewController Rotation - --(UIInterfaceOrientationMask)supportedInterfaceOrientations { - return UIInterfaceOrientationMaskAll; -} - -#pragma mark - Private methods -- (BOOL)allRequiredFieldsEntered { - if (self.requirePassword && [self.password length] == 0) - return NO; - - if (![self.email length] || !bit_validateEmail(self.email)) - return NO; - - return YES; -} - -#pragma mark - Table view data source - -- (NSInteger)numberOfSectionsInTableView:(UITableView *) __unused tableView { - return 2; -} - -- (NSInteger)tableView:(UITableView *) __unused tableView numberOfRowsInSection:(NSInteger)section { - if (section == 0) return 0; - - if(self.showsLoginViaWebButton) { - return 0; - } else { - NSInteger rows = 1; - - if ([self requirePassword]) rows ++; - - return rows; - } -} - -- (NSString *)tableView:(UITableView *) __unused tableView titleForFooterInSection:(NSInteger)section { - if (section == 0) { - return self.tableViewTitle; - } - - return nil; -} - -- (UITableViewCell *)tableView:(UITableView *) __unused tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - static NSString *CellIdentifier = @"InputCell"; - - UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier]; - if (cell == nil) { - cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; - - cell.accessoryType = UITableViewCellAccessoryNone; - cell.selectionStyle = UITableViewCellSelectionStyleNone; - cell.backgroundColor = [UIColor whiteColor]; - - UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(130, 11, self.view.frame.size.width - 130 - 25, 24)]; - if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad) { - textField.autoresizingMask = UIViewAutoresizingFlexibleWidth; - } - textField.adjustsFontSizeToFitWidth = YES; - textField.textColor = [UIColor blackColor]; - textField.backgroundColor = [UIColor lightGrayColor]; - - if (0 == [indexPath row]) { - textField.placeholder = BITHockeyLocalizedString(@"HockeyAuthenticationViewControllerEmailPlaceholder"); - textField.accessibilityHint = BITHockeyLocalizedString(@"HockeyAccessibilityHintRequired"); - textField.text = self.email; - self.emailField = textField; - - textField.keyboardType = UIKeyboardTypeEmailAddress; - if ([self requirePassword]) - textField.returnKeyType = UIReturnKeyNext; - else - textField.returnKeyType = UIReturnKeyDone; - - [textField addTarget:self action:@selector(userEmailEntered:) forControlEvents:UIControlEventEditingChanged]; - [textField becomeFirstResponder]; - } else { - textField.placeholder = BITHockeyLocalizedString(@"HockeyAuthenticationViewControllerPasswordPlaceholder"); - textField.text = self.password; - - textField.keyboardType = UIKeyboardTypeDefault; - textField.returnKeyType = UIReturnKeyDone; - textField.secureTextEntry = YES; - [textField addTarget:self action:@selector(userPasswordEntered:) forControlEvents:UIControlEventEditingChanged]; - } - - textField.backgroundColor = [UIColor whiteColor]; - textField.autocorrectionType = UITextAutocorrectionTypeNo; - textField.autocapitalizationType = UITextAutocapitalizationTypeNone; - textField.textAlignment = NSTextAlignmentLeft; - textField.delegate = self; - textField.tag = indexPath.row; - - textField.clearButtonMode = UITextFieldViewModeWhileEditing; - [textField setEnabled: YES]; - - [cell addSubview:textField]; - } - - if (0 == [indexPath row]) { - cell.textLabel.text = BITHockeyLocalizedString(@"HockeyAuthenticationViewControllerEmailDescription"); - } else { - cell.textLabel.text = BITHockeyLocalizedString(@"HockeyAuthenticationViewControllerPasswordDescription"); - } - - return cell; -} - - -- (void)userEmailEntered:(id)sender { - self.email = [(UITextField *)sender text]; - - self.navigationItem.rightBarButtonItem.enabled = [self allRequiredFieldsEntered]; -} - -- (void)userPasswordEntered:(id)sender { - self.password = [(UITextField *)sender text]; - - self.navigationItem.rightBarButtonItem.enabled = [self allRequiredFieldsEntered]; -} - -#pragma mark - UITextFieldDelegate - -- (BOOL)textFieldShouldReturn:(UITextField *)textField { - NSInteger nextTag = textField.tag + 1; - - UIResponder* nextResponder = [self.view viewWithTag:nextTag]; - if (nextResponder) { - [nextResponder becomeFirstResponder]; - } else { - if ([self allRequiredFieldsEntered]) { - if ([textField isFirstResponder]) - [textField resignFirstResponder]; - - [self saveAction:nil]; - } - } - return NO; -} - -#pragma mark - Actions -- (void)saveAction:(id) __unused sender { - [self setLoginUIEnabled:NO]; - - __weak typeof(self) weakSelf = self; - [self.delegate authenticationViewController:self - handleAuthenticationWithEmail:self.email - password:self.password - completion:^(BOOL succeeded, NSError *error) { - if(succeeded) { - //controller should dismiss us shortly.. - } else { - dispatch_async(dispatch_get_main_queue(), ^{ - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil - message:error.localizedDescription - preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *okAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"OK") - style:UIAlertActionStyleCancel - handler:^(UIAlertAction __unused *action) {}]; - [alertController addAction:okAction]; - [self presentViewController:alertController animated:YES completion:nil]; - typeof(self) strongSelf = weakSelf; - [strongSelf setLoginUIEnabled:YES]; - }); - } - }]; -} - -- (void) setLoginUIEnabled:(BOOL) enabled { - self.navigationItem.rightBarButtonItem.enabled = enabled; - self.tableView.userInteractionEnabled = enabled; -} - -@end - -#endif /* HOCKEYSDK_FEATURE_AUTHENTICATOR */ diff --git a/submodules/HockeySDK-iOS/Classes/BITAuthenticator.h b/submodules/HockeySDK-iOS/Classes/BITAuthenticator.h deleted file mode 100644 index a1eaac1ad6..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITAuthenticator.h +++ /dev/null @@ -1,385 +0,0 @@ -/* - * Author: Stephan Diederich, Andreas Linde - * - * Copyright (c) 2013-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import - -#import "BITHockeyBaseManager.h" - -/** - * Identification Types - */ -typedef NS_ENUM(NSUInteger, BITAuthenticatorIdentificationType) { - /** - * Assigns this app an anonymous user id. - *

- * The user will not be asked anything and an anonymous ID will be generated. - * This helps identifying this installation being unique but HockeyApp won't be able - * to identify who actually is running this installation and on which device - * the app is installed. - */ - BITAuthenticatorIdentificationTypeAnonymous, - /** - * Ask for the HockeyApp account email - *

- * This will present a user interface requesting the user to provide their - * HockeyApp user email address. - *

- * The provided email address has to match an email address of a registered - * HockeyApp user who is a member or tester of the app - */ - BITAuthenticatorIdentificationTypeHockeyAppEmail, - /** - * Ask for the HockeyApp account by email and password - *

- * This will present a user interface requesting the user to provide their - * HockeyApp user credentials. - *

- * The provided user account has to match a registered HockeyApp user who is - * a member or tester of the app - */ - BITAuthenticatorIdentificationTypeHockeyAppUser, - /** - * Identifies the current device - *

- * This will open the HockeyApp web page on the device in Safari and request the user - * to submit the device's unique identifier to the app. If the web page session is not aware - * of the current devices UDID, it will request the user to install the HockeyApp web clip - * which will provide the UDID to users session in the browser. - *

- * This requires the app to register an URL scheme. See the linked property and methods - * for further documentation on this. - */ - BITAuthenticatorIdentificationTypeDevice, - /** - * Ask for the HockeyApp account email. - *

- * This will present a user interface requesting the user to start a Safari based - * flow to login to HockeyApp (if not already logged in) and to share the hockeyapp - * account's email. - *

- * If restrictApplicationUsage is enabled, the provided user account has to match a - * registered HockeyApp user who is a member or tester of the app. - * For identification purpose any HockeyApp user is allowed. - */ - BITAuthenticatorIdentificationTypeWebAuth, -}; - -/** - * Restriction enforcement styles - * - * Specifies how often the Authenticator checks if the user is allowed to use - * this app. - */ -typedef NS_ENUM(NSUInteger, BITAuthenticatorAppRestrictionEnforcementFrequency) { - /** - * Checks if the user is allowed to use the app at the first time a version is started - */ - BITAuthenticatorAppRestrictionEnforcementOnFirstLaunch, - /** - * Checks if the user is allowed to use the app every time the app becomes active - */ - BITAuthenticatorAppRestrictionEnforcementOnAppActive, -}; - -@protocol BITAuthenticatorDelegate; - - -/** - * Identify and authenticate users of Ad-Hoc or Enterprise builds - * - * `BITAuthenticator` serves 2 purposes: - * - * 1. Identifying who is running your Ad-Hoc or Enterprise builds - * `BITAuthenticator` provides an identifier for the rest of the HockeySDK - * to work with, e.g. in-app update checks and crash reports. - * - * 2. Optional regular checking if an identified user is still allowed - * to run this application. The `BITAuthenticator` can be used to make - * sure only users who are testers of your app are allowed to run it. - * - * This module automatically disables itself when running in an App Store build by default! - * - * @warning It is mandatory to call `authenticateInstallation` somewhen after calling - * `[[BITHockeyManager sharedHockeyManager] startManager]` or fully customize the identification - * and validation workflow yourself. - * If your app shows a modal view on startup, make sure to call `authenticateInstallation` - * either once your modal view is fully presented (e.g. its `viewDidLoad:` method is processed) - * or once your modal view is dismissed. - */ -@interface BITAuthenticator : BITHockeyBaseManager - -#pragma mark - Configuration - - -///----------------------------------------------------------------------------- -/// @name Configuration -///----------------------------------------------------------------------------- - - -/** - * Defines the identification mechanism to be used - * - * _Default_: `BITAuthenticatorIdentificationTypeAnonymous` - * - * @see BITAuthenticatorIdentificationType - */ -@property (nonatomic, assign) BITAuthenticatorIdentificationType identificationType; - - -/** - * Enables or disables checking if the user is allowed to run this app - * - * If disabled, the Authenticator never validates, besides initial identification, - * if the user is allowed to run this application. - * - * If enabled, the Authenticator checks depending on `restrictionEnforcementFrequency` - * if the user is allowed to use this application. - * - * Enabling this property and setting `identificationType` to `BITAuthenticatorIdentificationTypeHockeyAppEmail`, - * `BITAuthenticatorIdentificationTypeHockeyAppUser` or `BITAuthenticatorIdentificationTypeWebAuth` also allows - * to remove access for users by removing them from the app's users list on HockeyApp. - * - * _Default_: `NO` - * - * @warning if `identificationType` is set to `BITAuthenticatorIdentificationTypeAnonymous`, - * this property has no effect. - * - * @see BITAuthenticatorIdentificationType - * @see restrictionEnforcementFrequency - */ -@property (nonatomic, assign) BOOL restrictApplicationUsage; - -/** - * Defines how often the BITAuthenticator checks if the user is allowed - * to run this application - * - * This requires `restrictApplicationUsage` to be enabled. - * - * _Default_: `BITAuthenticatorAppRestrictionEnforcementOnFirstLaunch` - * - * @see BITAuthenticatorAppRestrictionEnforcementFrequency - * @see restrictApplicationUsage - */ -@property (nonatomic, assign) BITAuthenticatorAppRestrictionEnforcementFrequency restrictionEnforcementFrequency; - -/** - * The authentication secret from HockeyApp. To find the right secret, - * click on your app on the HockeyApp dashboard, then on Show next to - * "Secret:". - * - * This is only needed if `identificationType` is set to `BITAuthenticatorIdentificationTypeHockeyAppEmail` - * - * @see identificationType - */ -@property (nonatomic, copy) NSString *authenticationSecret; - - -#pragma mark - Device based identification - -///----------------------------------------------------------------------------- -/// @name Device based identification -///----------------------------------------------------------------------------- - - -/** - * The baseURL of the webpage the user is redirected to if `identificationType` is - * set to `BITAuthenticatorIdentificationTypeDevice`; defaults to https://rink.hockeyapp.net. - * - * @see identificationType - */ -@property (nonatomic, strong) NSURL *webpageURL; - -/** - * URL to query the device's id via external webpage - * Built with the baseURL set in `webpageURL`. - */ -- (NSURL*) deviceAuthenticationURL; - -/** - * The url-scheme used to identify via `BITAuthenticatorIdentificationTypeDevice` - * - * Please make sure that the URL scheme is unique and not shared with other apps. - * - * If set to nil, the default scheme is used which is `ha`. - * - * @see identificationType - * @see handleOpenURL:sourceApplication:annotation: - */ -@property (nonatomic, copy) NSString *urlScheme; - -/** - Should be used by the app-delegate to forward handle application:openURL:sourceApplication:annotation: calls. - - This is required if `identificationType` is set to `BITAuthenticatorIdentificationTypeDevice`. - Your app needs to implement the default `ha` URL scheme or register its own scheme - via `urlScheme`. - BITAuthenticator checks if the given URL is actually meant to be parsed by it and will - return NO if it doesn't think so. It does this by checking the 'host'-part of the URL to be 'authorize', as well - as checking the protocol part. - Please make sure that if you're using a custom URL scheme, it does _not_ conflict with BITAuthenticator's. - If BITAuthenticator thinks the URL was meant to be an authorization URL, but could not find a valid token, it will - reset the stored identification token and state. - - Sample usage (in AppDelegate): - - - (BOOL)application:(UIApplication *)application - openURL:(NSURL *)url - sourceApplication:(NSString *)sourceApplication annotation:(id)annotation { - if ([[BITHockeyManager sharedHockeyManager].authenticator handleOpenURL:url - sourceApplication:sourceApplication - annotation:annotation]) { - return YES; - } else { - //do your own URL handling, return appropriate value - } - return NO; - } - - @param url Param `url` that was passed to the app - @param sourceApplication Param `sourceApplication` that was passed to the app - @param annotation Param `annotation` that was passed to the app - - @return YES if the URL request was handled, NO if the URL could not be handled/identified. - - @see identificationType - @see urlScheme - */ -- (BOOL) handleOpenURL:(NSURL *) url - sourceApplication:(NSString *) sourceApplication - annotation:(id) annotation; - -#pragma mark - Authentication - -///----------------------------------------------------------------------------- -/// @name Authentication -///----------------------------------------------------------------------------- - -/** - * Invoked automatic identification and validation - * - * If the `BITAuthenticator` is in automatic mode this will initiate identifying - * the current user according to the type specified in `identificationType` and - * validate if the identified user is allowed to run this application. - * - * If the user is not yet identified it will present a modal view asking the user to - * provide the required information. - * - * If your app provides it's own startup modal screen, e.g. a guide or a login, then - * you might either call this method once that UI is fully presented or once - * the user e.g. did actually login already. - * - * @warning You need to call this method in your code even if automatic mode is enabled! - * - * @see identificationType - */ -- (void) authenticateInstallation; - -/** - * Identifies the user according to the type specified in `identificationType`. - * - * If the `BITAuthenticator` is in manual mode, it's your responsibility to call - * this method. Depending on the `identificationType`, this method - * might present a viewController to let the user enter his/her credentials. - * - * If the Authenticator is in auto-mode, this is called by the authenticator itself - * once needed. - * - * @see identificationType - * @see authenticateInstallation - * @see validateWithCompletion: - * - * @param completion Block being executed once identification completed. Be sure to properly dispatch code to the main queue if necessary. - */ -- (void) identifyWithCompletion:(void(^)(BOOL identified, NSError *error)) completion; - -/** - * Returns YES if this app is identified according to the setting in `identificationType`. - * - * Since the identification process is done asynchronously (contacting the server), - * you need to observe the value change via KVO. - * - * @see identificationType - */ -@property (nonatomic, assign, readonly, getter = isIdentified) BOOL identified; - -/** - * Validates if the identified user is allowed to run this application. This checks - * with the HockeyApp backend and calls the completion-block once completed. - * - * If the `BITAuthenticator` is in manual mode, it's your responsibility to call - * this method. If the application is not yet identified, validation is not possible - * and the completion-block is called with an error set. - * - * If the `BITAuthenticator` is in auto-mode, this is called by the authenticator itself - * once needed. - * - * @see identificationType - * @see authenticateInstallation - * @see identifyWithCompletion: - * - * @param completion Block being executed once validation completed. Be sure to properly dispatch code to the main queue if necessary. - */ -- (void) validateWithCompletion:(void(^)(BOOL validated, NSError *error)) completion; - -/** - * Indicates if this installation is validated. - */ -@property (nonatomic, assign, readonly, getter = isValidated) BOOL validated; - -/** - * Removes all previously stored authentication tokens, UDIDs, etc. - */ -- (void) cleanupInternalStorage; - -/** - * Returns different values depending on `identificationType`. This can be used - * by the application to identify the user. - * - * @see identificationType - */ -- (NSString*) publicInstallationIdentifier; -@end - -#pragma mark - Protocol - -/** - * `BITAuthenticator` protocol - */ -@protocol BITAuthenticatorDelegate - -@optional -/** - * If the authentication (or validation) needs to identify the user, - * this delegate method is called with the viewController that we'll present. - * - * @param authenticator `BITAuthenticator` object - * @param viewController `UIViewController` used to identify the user - * - */ -- (void) authenticator:(BITAuthenticator *)authenticator willShowAuthenticationController:(UIViewController*) viewController; -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITAuthenticator.m b/submodules/HockeySDK-iOS/Classes/BITAuthenticator.m deleted file mode 100644 index f197219992..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITAuthenticator.m +++ /dev/null @@ -1,990 +0,0 @@ -/* - * Author: Stephan Diederich - * - * Copyright (c) 2013-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_AUTHENTICATOR - -#import "HockeySDKPrivate.h" -#import "BITAuthenticator_Private.h" -#import "BITAuthenticationViewController.h" -#import "BITHockeyAppClient.h" -#import "BITHockeyHelper.h" -#import "BITHockeyHelper+Application.h" -#import "BITHockeyBaseManagerPrivate.h" - -#include - -static NSString *const kBITAuthenticatorUUIDKey = @"BITAuthenticatorUUIDKey"; -static NSString *const kBITAuthenticatorIdentifierKey = @"BITAuthenticatorIdentifierKey"; -static NSString *const kBITAuthenticatorIdentifierTypeKey = @"BITAuthenticatorIdentifierTypeKey"; -static NSString *const kBITAuthenticatorLastAuthenticatedVersionKey = @"BITAuthenticatorLastAuthenticatedVersionKey"; -static NSString *const kBITAuthenticatorUserEmailKey = @"BITAuthenticatorUserEmailKey"; - -//deprecated -static NSString *const kBITAuthenticatorAuthTokenKey = @"BITAuthenticatorAuthTokenKey"; -static NSString *const kBITAuthenticatorAuthTokenTypeKey = @"BITAuthenticatorAuthTokenTypeKey"; - -typedef unsigned int bit_uint32; -static unsigned char kBITPNGHeader[8] = {137, 80, 78, 71, 13, 10, 26, 10}; -static unsigned char kBITPNGEndChunk[4] = {0x49, 0x45, 0x4e, 0x44}; - - -@interface BITAuthenticator() - -@property (nonatomic, assign) BOOL isSetup; - -@property (nonatomic, strong) id appDidBecomeActiveObserver; -@property (nonatomic, strong) id appDidEnterBackgroundObserver; -@property (nonatomic, strong) UIViewController *authenticationController; - -@end - - -@implementation BITAuthenticator - -- (instancetype)initWithAppIdentifier:(NSString *)appIdentifier appEnvironment:(BITEnvironment)environment { - self = [super initWithAppIdentifier:appIdentifier appEnvironment:environment]; - if (self) { - _webpageURL = [NSURL URLWithString:@"https://rink.hockeyapp.net/"]; - - _identificationType = BITAuthenticatorIdentificationTypeAnonymous; - _isSetup = NO; - _restrictApplicationUsage = NO; - _restrictionEnforcementFrequency = BITAuthenticatorAppRestrictionEnforcementOnFirstLaunch; - } - return self; -} - -- (void)dealloc { - [self unregisterObservers]; -} - -#pragma mark - BITHockeyBaseManager overrides - -- (void)startManager { - //disabled in TestFlight and the AppStore - if (self.appEnvironment != BITEnvironmentOther) { return; } - - self.isSetup = YES; -} - -#pragma mark - - -- (void)authenticateInstallation { - //disabled in TestFlight and the AppStore - if (self.appEnvironment != BITEnvironmentOther) { return; } - - // make sure this is called after startManager so all modules are fully setup - if (!self.isSetup) { - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(authenticateInstallation) object:nil]; - [self performSelector:@selector(authenticateInstallation) withObject:nil afterDelay:0.1]; - } else { - switch ([BITHockeyHelper applicationState]) { - case BITApplicationStateActive: - [self authenticate]; - break; - case BITApplicationStateBackground: - case BITApplicationStateInactive: - case BITApplicationStateUnknown: - // do nothing, wait for active state - break; - } - } - [self registerObservers]; -} - -- (void)authenticate { - [self identifyWithCompletion:^(BOOL identified, NSError *error) { - if (identified) { - if ([self needsValidation]) { - [self validate]; - } else { - [self dismissAuthenticationControllerAnimated:YES completion:nil]; - } - } else { - BITHockeyLogError(@"Failed to identify. Error: %@", error); - } - }]; -} - -#pragma mark - Identification - -- (void)identifyWithCompletion:(void (^)(BOOL identified, NSError *))completion { - if (self.authenticationController) { - BITHockeyLogDebug(@"Authentication controller already visible. Ignoring identify request"); - if (completion) { completion(NO, nil); } - return; - } - //first check if the stored identification type matches the one currently configured - NSString *storedTypeString = [self stringValueFromKeychainForKey:kBITAuthenticatorIdentifierTypeKey]; - NSString *configuredTypeString = [self.class stringForIdentificationType:self.identificationType]; - if (storedTypeString && ![storedTypeString isEqualToString:configuredTypeString]) { - BITHockeyLogDebug(@"Identification type mismatch for stored auth-token. Resetting."); - [self storeInstallationIdentifier:nil withType:BITAuthenticatorIdentificationTypeAnonymous]; - } - - NSString *identification = [self installationIdentifier]; - - if (identification) { - self.identified = YES; - if (completion) { completion(YES, nil); } - return; - } - - [self processFullSizeImage]; - if (self.identified) { - if (completion) { completion(YES, nil); } - return; - } - - //it's not identified yet, do it now - BITAuthenticationViewController *viewController = nil; - switch (self.identificationType) { - case BITAuthenticatorIdentificationTypeAnonymous: - [self storeInstallationIdentifier:bit_UUID() withType:BITAuthenticatorIdentificationTypeAnonymous]; - self.identified = YES; - if (completion) { completion(YES, nil); } - return; - case BITAuthenticatorIdentificationTypeHockeyAppUser: - viewController = [[BITAuthenticationViewController alloc] initWithDelegate:self]; - viewController.requirePassword = YES; - viewController.tableViewTitle = BITHockeyLocalizedString(@"HockeyAuthenticationViewControllerDataEmailAndPasswordDescription"); - break; - case BITAuthenticatorIdentificationTypeDevice: - viewController = [[BITAuthenticationViewController alloc] initWithDelegate:self]; - viewController.requirePassword = NO; - viewController.showsLoginViaWebButton = YES; - viewController.tableViewTitle = BITHockeyLocalizedString(@"HockeyAuthenticationViewControllerWebUDIDLoginDescription"); - break; - case BITAuthenticatorIdentificationTypeWebAuth: - viewController = [[BITAuthenticationViewController alloc] initWithDelegate:self]; - viewController.requirePassword = NO; - viewController.showsLoginViaWebButton = YES; - viewController.tableViewTitle = BITHockeyLocalizedString(@"HockeyAuthenticationViewControllerWebAuthLoginDescription"); - break; - case BITAuthenticatorIdentificationTypeHockeyAppEmail: - if (nil == self.authenticationSecret) { - NSError *error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain - code:BITAuthenticatorAuthorizationSecretMissing - userInfo:@{NSLocalizedDescriptionKey:@"For email identification, the authentication secret must be set"}]; - if (completion) { completion(NO, error); } - return; - } - viewController = [[BITAuthenticationViewController alloc] initWithDelegate:self]; - viewController.requirePassword = NO; - viewController.tableViewTitle = BITHockeyLocalizedString(@"HockeyAuthenticationViewControllerDataEmailDescription"); - break; - } - id strongDelegate = self.delegate; - if ([strongDelegate respondsToSelector:@selector(authenticator:willShowAuthenticationController:)]) { - [strongDelegate authenticator:self willShowAuthenticationController:viewController]; - } - - NSAssert(viewController, @"ViewController should've been created"); - - viewController.email = [self stringValueFromKeychainForKey:kBITAuthenticatorUserEmailKey]; - self.authenticationController = viewController; - self.identificationCompletion = completion; - dispatch_async(dispatch_get_main_queue(), ^{ - [self showView:viewController]; - }); -} - -#pragma mark - Validation - -- (BOOL)needsValidation { - if (BITAuthenticatorIdentificationTypeAnonymous == self.identificationType) { - return NO; - } - if (NO == self.restrictApplicationUsage) { - return NO; - } - if (self.restrictionEnforcementFrequency == BITAuthenticatorAppRestrictionEnforcementOnFirstLaunch && - ![self.executableUUID isEqualToString:self.lastAuthenticatedVersion]) { - return YES; - } - if (NO == self.isValidated && self.restrictionEnforcementFrequency == BITAuthenticatorAppRestrictionEnforcementOnAppActive) { - return YES; - } - return NO; -} - -- (void)validate { - [self validateWithCompletion:^(BOOL validated, NSError *error) { - dispatch_async(dispatch_get_main_queue(), ^{ - if (validated) { - [self dismissAuthenticationControllerAnimated:YES completion:nil]; - } else { - BITHockeyLogError(@"Validation failed with error: %@", error); - __weak typeof(self) weakSelf = self; - - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil - message:error.localizedDescription - preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *okAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"HockeyOK") - style:UIAlertActionStyleDefault - handler:^(UIAlertAction __unused *action) { - typeof(self) strongSelf = weakSelf; - [strongSelf validate]; - }]; - - [alertController addAction:okAction]; - [self showAlertController:alertController]; - } - }); - }]; -} - -- (void)validateWithCompletion:(void (^)(BOOL validated, NSError *))completion { - BOOL requirementsFulfilled = YES; - NSError *error = nil; - switch (self.identificationType) { - case BITAuthenticatorIdentificationTypeAnonymous: { - error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain - code:BITAuthenticatorErrorUnknown - userInfo:@{NSLocalizedDescriptionKey:@"Anonymous users can't be validated"}]; - requirementsFulfilled = NO; - break; - } - case BITAuthenticatorIdentificationTypeHockeyAppEmail: - if (nil == self.authenticationSecret) { - error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain - code:BITAuthenticatorAuthorizationSecretMissing - userInfo:@{NSLocalizedDescriptionKey:@"For email validation, the authentication secret must be set"}]; - requirementsFulfilled = NO; - break; - } - //no break - case BITAuthenticatorIdentificationTypeDevice: - case BITAuthenticatorIdentificationTypeHockeyAppUser: - case BITAuthenticatorIdentificationTypeWebAuth: - if (nil == self.installationIdentifier) { - error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain - code:BITAuthenticatorNotIdentified - userInfo:@{NSLocalizedDescriptionKey:@"Make sure to identify the installation first"}]; - requirementsFulfilled = NO; - } - break; - } - if (NO == requirementsFulfilled) { - if (completion) { - completion(NO, error); - } - return; - } - - NSString *validationPath = [NSString stringWithFormat:@"api/3/apps/%@/identity/validate", self.encodedAppIdentifier]; - - __weak typeof(self) weakSelf = self; - NSURLRequest *request = [self.hockeyAppClient requestWithMethod:@"GET" path:validationPath parameters:[self validationParameters]]; - - NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; - __block NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration]; - - NSURLSessionDataTask *task = [session dataTaskWithRequest:request - completionHandler:^(NSData *data, NSURLResponse __unused *response, NSError *innerError) { - typeof(self) strongSelf = weakSelf; - - [session finishTasksAndInvalidate]; - - [strongSelf handleValidationResponseWithData:data error:innerError completion:completion]; - }]; - [task resume]; -} - -- (void)handleValidationResponseWithData:(NSData *)responseData error:(NSError *)error completion:(void (^)(BOOL validated, NSError *))completion { - if (nil == responseData) { - NSDictionary *userInfo = @{NSLocalizedDescriptionKey:BITHockeyLocalizedString(@"HockeyAuthenticationFailedAuthenticate")}; - if (error) { - NSMutableDictionary *dict = [userInfo mutableCopy]; - dict[NSUnderlyingErrorKey] = error; - userInfo = dict; - } - NSError *localError = [NSError errorWithDomain:kBITAuthenticatorErrorDomain - code:BITAuthenticatorNetworkError - userInfo:userInfo]; - self.validated = NO; - if (completion) { completion(NO, localError); } - } else { - NSError *validationParseError = nil; - BOOL valid = [self.class isValidationResponseValid:responseData error:&validationParseError]; - self.validated = valid; - if (valid) { - [self setLastAuthenticatedVersion:self.executableUUID]; - } - if (completion) { completion(valid, validationParseError); } - } -} - -- (NSDictionary *)validationParameters { - NSParameterAssert(self.installationIdentifier); - NSParameterAssert(self.installationIdentifierParameterString); - - NSString *installString = bit_appAnonID(NO); - if (installString) { - return @{self.installationIdentifierParameterString:self.installationIdentifier, @"install_string":installString}; - } - - return @{self.installationIdentifierParameterString:self.installationIdentifier}; -} - -+ (BOOL)isValidationResponseValid:(id)response error:(NSError *__autoreleasing *)error { - NSParameterAssert(response); - - NSError *jsonParseError = nil; - id jsonObject = [NSJSONSerialization JSONObjectWithData:response - options:0 - error:&jsonParseError]; - if (nil == jsonObject) { - if (error) { - *error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain - code:BITAuthenticatorAPIServerReturnedInvalidResponse - userInfo:@{NSLocalizedDescriptionKey:BITHockeyLocalizedString(@"HockeyAuthenticationFailedAuthenticate")}]; - } - return NO; - } - if (![jsonObject isKindOfClass:[NSDictionary class]]) { - if (error) { - *error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain - code:BITAuthenticatorAPIServerReturnedInvalidResponse - userInfo:@{NSLocalizedDescriptionKey:BITHockeyLocalizedString(@"HockeyAuthenticationFailedAuthenticate")}]; - } - return NO; - } - - NSString *status = jsonObject[@"status"]; - if ([status isEqualToString:@"not authorized"]) { - if (error) { - *error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain - code:BITAuthenticatorNotAuthorized - userInfo:@{NSLocalizedDescriptionKey:BITHockeyLocalizedString(@"HockeyAuthenticationNotMember")}]; - } - return NO; - } else if ([status isEqualToString:@"not found"]) { - if (error) { - *error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain - code:BITAuthenticatorUnknownApplicationID - userInfo:@{NSLocalizedDescriptionKey:BITHockeyLocalizedString(@"HockeyAuthenticationContactDeveloper")}]; - } - return NO; - } else if ([status isEqualToString:@"validated"]) { - return YES; - } else { - if (error) { - *error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain - code:BITAuthenticatorAPIServerReturnedInvalidResponse - userInfo:@{NSLocalizedDescriptionKey:BITHockeyLocalizedString(@"HockeyAuthenticationFailedAuthenticate")}]; - } - return NO; - } -} - -#pragma mark - AuthenticationViewController Helper - -/** - * This method has to be called on the main queue - */ -- (void)dismissAuthenticationControllerAnimated:(BOOL)animated completion:(void (^)(void))completion { - if (!self.authenticationController) { return; } - UIViewController *presentingViewController = [self.authenticationController presentingViewController]; - - // If there is no presenting view controller just remove view - if (presentingViewController) { - [self.authenticationController dismissViewControllerAnimated:animated completion:completion]; - } else { - [self.authenticationController.navigationController.view removeFromSuperview]; - if (completion) { - completion(); - } - } - self.authenticationController = nil; -} - -#pragma mark - AuthenticationViewControllerDelegate - -- (void)authenticationViewController:(UIViewController *)viewController - handleAuthenticationWithEmail:(NSString *)email - password:(NSString *)password - completion:(void (^)(BOOL, NSError *))completion { - - NSParameterAssert(email && email.length); - NSParameterAssert(self.identificationType == BITAuthenticatorIdentificationTypeHockeyAppEmail || (password && password.length)); - - // Trim whitespace from email in case the user has added a whitespace at the end of the email address. This shouldn't - // happen if devs use our UI but we've had 1-2 support tickets where the email contained whitespace at the end and - // verification failed because of that. - email = [email stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; - - NSURLRequest *request = [self requestForAuthenticationEmail:email password:password]; - - [self authenticationViewController:viewController handleAuthenticationWithEmail:email request:request completion:completion]; -} - -- (void)authenticationViewController:(UIViewController *) __unused viewController - handleAuthenticationWithEmail:(NSString *)email - request:(NSURLRequest *)request - completion:(void (^)(BOOL, NSError *))completion { - - __weak typeof(self) weakSelf = self; - NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; - __block NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration]; - - NSURLSessionDataTask *task = [session dataTaskWithRequest:request - completionHandler:^(NSData *data, NSURLResponse *response, NSError __unused *error) { - typeof(self) strongSelf = weakSelf; - NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; - - [session finishTasksAndInvalidate]; - - [strongSelf handleAuthenticationWithResponse:httpResponse email:email data:data completion:completion]; - }]; - [task resume]; -} - -- (void)authenticationViewControllerDidTapWebButton:(UIViewController *) __unused viewController { - NSURL *url = [self deviceAuthenticationURL]; - if (url) { - [[UIApplication sharedApplication] openURL:url]; - } -} - -#pragma mark - Networking - -- (void)handleAuthenticationWithResponse:(NSHTTPURLResponse *)response email:(NSString *)email data:(NSData *)data completion:(void (^)(BOOL, NSError *))completion { - NSError *authParseError = nil; - NSString *authToken = [self.class authenticationTokenFromURLResponse:response - data:data - error:&authParseError]; - BOOL identified; - if (authToken) { - identified = YES; - [self storeInstallationIdentifier:authToken withType:self.identificationType]; - dispatch_async(dispatch_get_main_queue(), ^{ - [self dismissAuthenticationControllerAnimated:YES completion:nil]; - }); - BOOL success = [self addStringValueToKeychain:email forKey:kBITAuthenticatorUserEmailKey]; - if (!success) { - [self alertOnFailureStoringTokenInKeychain]; - } - } else { - identified = NO; - } - self.identified = identified; - if (completion) { completion(identified, authParseError); } - if (self.identificationCompletion) { - self.identificationCompletion(identified, authParseError); - self.identificationCompletion = nil; - } -} - -- (NSURLRequest *)requestForAuthenticationEmail:(NSString *)email password:(NSString *)password { - NSString *authenticationPath = [self authenticationPath]; - NSMutableDictionary *params = [NSMutableDictionary dictionary]; - - NSString *installString = bit_appAnonID(NO); - if (installString) { - params[@"install_string"] = installString; - } - - if (BITAuthenticatorIdentificationTypeHockeyAppEmail == self.identificationType) { - NSString *authCode = BITHockeyMD5([NSString stringWithFormat:@"%@%@", - self.authenticationSecret ?: @"", - email ?: @""]); - - params[@"email"] = email ?: @""; - params[@"authcode"] = authCode.lowercaseString; - } - - NSMutableURLRequest *request = [self.hockeyAppClient requestWithMethod:@"POST" - path:authenticationPath - parameters:params]; - if (BITAuthenticatorIdentificationTypeHockeyAppUser == self.identificationType) { - NSString *authStr = [NSString stringWithFormat:@"%@:%@", email, password]; - NSData *authData = [authStr dataUsingEncoding:NSUTF8StringEncoding]; - NSString *authValue = [NSString stringWithFormat:@"Basic %@", [authData base64EncodedStringWithOptions:0]]; - [request setValue:authValue forHTTPHeaderField:@"Authorization"]; - } - - return request; -} - -- (NSString *)authenticationPath { - if (BITAuthenticatorIdentificationTypeHockeyAppUser == self.identificationType) { - return [NSString stringWithFormat:@"api/3/apps/%@/identity/authorize", self.encodedAppIdentifier]; - } else { - return [NSString stringWithFormat:@"api/3/apps/%@/identity/check", self.encodedAppIdentifier]; - } -} - -+ (NSString *)authenticationTokenFromURLResponse:(NSHTTPURLResponse *)urlResponse data:(NSData *)data error:(NSError *__autoreleasing *)error { - if (nil == urlResponse) { - if (error) { - *error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain - code:BITAuthenticatorAPIServerReturnedInvalidResponse - userInfo:@{NSLocalizedDescriptionKey:BITHockeyLocalizedString(@"HockeyAuthenticationFailedAuthenticate")}]; - } - return nil; - } - - switch (urlResponse.statusCode) { - case 401: - if (error) { - *error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain - code:BITAuthenticatorNotAuthorized - userInfo:@{ - NSLocalizedDescriptionKey:BITHockeyLocalizedString(@"HockeyAuthenticationWrongEmailPassword") - }]; - } - break; - case 200: - case 404: - //Do nothing, handled below - break; - default: - if (error) { - *error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain - code:BITAuthenticatorAPIServerReturnedInvalidResponse - userInfo:@{NSLocalizedDescriptionKey:BITHockeyLocalizedString(@"HockeyAuthenticationFailedAuthenticate")}]; - - } - break; - } - if (200 != urlResponse.statusCode && 404 != urlResponse.statusCode) { - //make sure we have an error created if user wanted to have one - NSParameterAssert(nil == error || *error); - return nil; - } - - NSError *jsonParseError = nil; - id jsonObject = [NSJSONSerialization JSONObjectWithData:data - options:0 - error:&jsonParseError]; - //no json or unexpected json - if (nil == jsonObject || ![jsonObject isKindOfClass:[NSDictionary class]]) { - if (error) { - NSDictionary *userInfo = @{NSLocalizedDescriptionKey:BITHockeyLocalizedString(@"HockeyAuthenticationFailedAuthenticate")}; - if (jsonParseError) { - NSMutableDictionary *userInfoMutable = [userInfo mutableCopy]; - userInfoMutable[NSUnderlyingErrorKey] = jsonParseError; - userInfo = userInfoMutable; - } - *error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain - code:BITAuthenticatorAPIServerReturnedInvalidResponse - userInfo:userInfo]; - } - return nil; - } - - NSString *status = jsonObject[@"status"]; - NSString *authToken = nil; - if ([status isEqualToString:@"identified"]) { - authToken = jsonObject[@"iuid"]; - } else if ([status isEqualToString:@"authorized"]) { - authToken = jsonObject[@"auid"]; - } else if ([status isEqualToString:@"not authorized"]) { - if (error) { - *error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain - code:BITAuthenticatorNotAuthorized - userInfo:@{NSLocalizedDescriptionKey:BITHockeyLocalizedString(@"HockeyAuthenticationNotMember")}]; - - } - } - //if no error is set yet, but error parameter is given, return a generic error - if (nil == authToken && error && nil == *error) { - *error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain - code:BITAuthenticatorAPIServerReturnedInvalidResponse - userInfo:@{NSLocalizedDescriptionKey:BITHockeyLocalizedString(@"HockeyAuthenticationFailedAuthenticate")}]; - } - return authToken; -} - -- (NSURL *)deviceAuthenticationURL { - NSString *whatParameter = nil; - switch (self.identificationType) { - case BITAuthenticatorIdentificationTypeWebAuth: - whatParameter = @"email"; - break; - case BITAuthenticatorIdentificationTypeDevice: - whatParameter = @"udid"; - break; - case BITAuthenticatorIdentificationTypeAnonymous: - case BITAuthenticatorIdentificationTypeHockeyAppEmail: - case BITAuthenticatorIdentificationTypeHockeyAppUser: - return nil; - } - NSURL *url = [self.webpageURL URLByAppendingPathComponent:[NSString stringWithFormat:@"apps/%@/authorize", self.encodedAppIdentifier]]; - NSParameterAssert(whatParameter && url.absoluteString); - url = [NSURL URLWithString:[NSString stringWithFormat:@"%@?what=%@", url.absoluteString, whatParameter]]; - return url; -} - -- (BOOL)handleOpenURL:(NSURL *)url - sourceApplication:(NSString *) __unused sourceApplication - annotation:(id)annotation { - //check if this URL was meant for us, if not return NO so the user can - //handle it - NSString *const kAuthorizationHost = @"authorize"; - NSString *urlScheme = self.urlScheme ?: [NSString stringWithFormat:@"ha%@", self.appIdentifier]; - if (!([[url scheme] isEqualToString:urlScheme] && [[url host] isEqualToString:kAuthorizationHost])) { - BITHockeyLogWarning(@"WARNING: URL scheme for authentication doesn't match!"); - return NO; - } - - NSString *installationIdentifier = nil; - NSString *localizedErrorDescription = nil; - switch (self.identificationType) { - case BITAuthenticatorIdentificationTypeWebAuth: { - NSString *email = nil; - [self.class email:&email andIUID:&installationIdentifier fromOpenURL:url]; - if (email) { - BOOL success = [self addStringValueToKeychain:email forKey:kBITAuthenticatorUserEmailKey]; - if (!success) { - [self alertOnFailureStoringTokenInKeychain]; - } - } else { - BITHockeyLogDebug(@"No email found in URL: %@", url); - } - localizedErrorDescription = @"Failed to retrieve parameters from URL."; - break; - } - case BITAuthenticatorIdentificationTypeDevice: { - installationIdentifier = [self.class UDIDFromOpenURL:url annotation:annotation]; - localizedErrorDescription = @"Failed to retrieve UDID from URL."; - break; - } - case BITAuthenticatorIdentificationTypeHockeyAppEmail: - case BITAuthenticatorIdentificationTypeAnonymous: - case BITAuthenticatorIdentificationTypeHockeyAppUser: - return NO; - } - - if (installationIdentifier) { - BITHockeyLogDebug(@"Authentication succeeded."); - if (NO == self.restrictApplicationUsage) { - [self dismissAuthenticationControllerAnimated:YES completion:nil]; - } - [self storeInstallationIdentifier:installationIdentifier withType:self.identificationType]; - self.identified = YES; - if (self.identificationCompletion) { - self.identificationCompletion(YES, nil); - self.identificationCompletion = nil; - } - } else { - //reset token - BITHockeyLogDebug(@"Resetting authentication token"); - [self storeInstallationIdentifier:nil withType:self.identificationType]; - self.identified = NO; - if (self.identificationCompletion) { - NSError *error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain - code:BITAuthenticatorErrorUnknown - userInfo:@{NSLocalizedDescriptionKey:localizedErrorDescription}]; - self.identificationCompletion(NO, error); - self.identificationCompletion = nil; - } - } - return YES; -} - -+ (NSString *)UDIDFromOpenURL:(NSURL *)url annotation:(id) __unused annotation { - NSString *query = [url query]; - NSString *udid = nil; - //there should actually only one - static NSString *const UDIDQuerySpecifier = @"udid"; - for (NSString *queryComponents in [query componentsSeparatedByString:@"&"]) { - NSArray *parameterComponents = [queryComponents componentsSeparatedByString:@"="]; - if (2 == parameterComponents.count && [parameterComponents[0] isEqualToString:UDIDQuerySpecifier]) { - udid = parameterComponents[1]; - break; - } - } - return udid; -} - -+ (void)email:(NSString *__autoreleasing *)email andIUID:(NSString *__autoreleasing *)iuid fromOpenURL:(NSURL *)url { - NSString *query = [url query]; - //there should actually only one - static NSString *const EmailQuerySpecifier = @"email"; - static NSString *const IUIDQuerySpecifier = @"iuid"; - for (NSString *queryComponents in [query componentsSeparatedByString:@"&"]) { - NSArray *parameterComponents = [queryComponents componentsSeparatedByString:@"="]; - if (email && 2 == parameterComponents.count && [parameterComponents[0] isEqualToString:EmailQuerySpecifier]) { - *email = parameterComponents[1]; - } else if (iuid && 2 == parameterComponents.count && [parameterComponents[0] isEqualToString:IUIDQuerySpecifier]) { - *iuid = parameterComponents[1]; - } - } -} - -#pragma mark - Private helpers - -- (void)alertOnFailureStoringTokenInKeychain { - if ([BITHockeyHelper applicationState] != BITApplicationStateActive) { - return; - } - - BITHockeyLogError(@"[HockeySDK] ERROR: The authentication token could not be stored due to a keychain error. This is most likely a signing or keychain entitlement issue!"); -} - -- (void)cleanupInternalStorage { - [self removeKeyFromKeychain:kBITAuthenticatorIdentifierTypeKey]; - [self removeKeyFromKeychain:kBITAuthenticatorIdentifierKey]; - [self removeKeyFromKeychain:kBITAuthenticatorUUIDKey]; - [self removeKeyFromKeychain:kBITAuthenticatorUserEmailKey]; - [self setLastAuthenticatedVersion:nil]; - self.identified = NO; - self.validated = NO; - - //cleanup values stored from 3.5 Beta1..Beta3 - [self removeKeyFromKeychain:kBITAuthenticatorAuthTokenKey]; - [self removeKeyFromKeychain:kBITAuthenticatorAuthTokenTypeKey]; -} - -- (void)processFullSizeImage { -#ifdef BIT_INTERNAL_DEBUG - NSString* path = [[NSBundle mainBundle] pathForResource:@"iTunesArtwork" ofType:@"png"]; -#else - NSString *path = [[[NSBundle mainBundle] bundlePath] stringByAppendingString:@"/../iTunesArtwork"]; -#endif - - struct stat fs; - int fd = open([path UTF8String], O_RDONLY, 0); - if (fstat(fd, &fs) < 0) { - // File not found - close(fd); - return; - } - - BITHockeyLogDebug(@"Processing full size image for possible authentication"); - - unsigned char *buffer, *source; - source = (unsigned char *)malloc((unsigned long)fs.st_size); - if (read(fd, source, (unsigned long)fs.st_size) != fs.st_size) { - close(fd); - // Couldn't read file - free(source); - return; - } - - if ((fs.st_size < 20) || (memcmp(source, kBITPNGHeader, 8))) { - // Not a PNG - free(source); - return; - } - - buffer = source + 8; - - NSString *result = nil; - bit_uint32 length; - unsigned char *name; - unsigned char *data; - int chunk_index = 0; - long long bytes_left = fs.st_size - 8; - do { - memcpy(&length, buffer, 4); - length = ntohl(length); - - buffer += 4; - name = (unsigned char *)malloc(5); - name[4] = 0; - memcpy(name, buffer, 4); - - buffer += 4; - data = (unsigned char *)malloc(length + 1); - - if (bytes_left >= length) { - memcpy(data, buffer, length); - - buffer += length; - buffer += 4; - if (!strcmp((const char *)name, "tEXt")) { - data[length] = 0; - NSString *key = [NSString stringWithCString:(char *)data encoding:NSUTF8StringEncoding]; - - if ([key isEqualToString:@"Data"]) { - result = [NSString stringWithCString:(char *)(data + key.length + 1) encoding:NSUTF8StringEncoding]; - } - } - - if (!memcmp(name, kBITPNGEndChunk, 4)) { - chunk_index = 128; - } - } - - free(data); - free(name); - - bytes_left -= (length + 3 * 4); - } while ((chunk_index++ < 128) && (bytes_left > 8)); - - free(source); - - if (result) { - BITHockeyLogDebug(@"Authenticating using full size image information: %@", result); - [self handleOpenURL:[NSURL URLWithString:result] sourceApplication:nil annotation:nil]; - } else { - BITHockeyLogDebug(@"No authentication information found"); - } -} - -#pragma mark - NSNotification - -- (void)registerObservers { - __weak typeof(self) weakSelf = self; - if (nil == self.appDidBecomeActiveObserver) { - self.appDidBecomeActiveObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification - object:nil - queue:NSOperationQueue.mainQueue - usingBlock:^(NSNotification *note) { - typeof(self) strongSelf = weakSelf; - [strongSelf applicationDidBecomeActive:note]; - }]; - } - if (nil == self.appDidEnterBackgroundObserver) { - self.appDidEnterBackgroundObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification - object:nil - queue:NSOperationQueue.mainQueue - usingBlock:^(NSNotification *note) { - typeof(self) strongSelf = weakSelf; - [strongSelf applicationDidEnterBackground:note]; - }]; - } -} - -- (void)unregisterObservers { - if (self.appDidBecomeActiveObserver) { - [[NSNotificationCenter defaultCenter] removeObserver:self.appDidBecomeActiveObserver]; - self.appDidBecomeActiveObserver = nil; - } - if (self.appDidEnterBackgroundObserver) { - [[NSNotificationCenter defaultCenter] removeObserver:self.appDidEnterBackgroundObserver]; - self.appDidEnterBackgroundObserver = nil; - } -} - -#pragma mark - Property overrides - -- (void)storeInstallationIdentifier:(NSString *)installationIdentifier withType:(BITAuthenticatorIdentificationType)type { - if (nil == installationIdentifier) { - [self removeKeyFromKeychain:kBITAuthenticatorIdentifierKey]; - [self removeKeyFromKeychain:kBITAuthenticatorIdentifierTypeKey]; - } else { - BOOL success1 = [self addStringValueToKeychainForThisDeviceOnly:installationIdentifier - forKey:kBITAuthenticatorIdentifierKey]; - BOOL success2 = [self addStringValueToKeychainForThisDeviceOnly:[self.class stringForIdentificationType:type] - forKey:kBITAuthenticatorIdentifierTypeKey]; - if (!success1 || !success2) { - [self alertOnFailureStoringTokenInKeychain]; - } - } -} - -- (NSString *)installationIdentifier { - NSString *identifier = [self stringValueFromKeychainForKey:kBITAuthenticatorIdentifierKey]; - return identifier; -} - -- (void)setLastAuthenticatedVersion:(NSString *)lastAuthenticatedVersion { - NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; - if (nil == lastAuthenticatedVersion) { - [defaults removeObjectForKey:kBITAuthenticatorLastAuthenticatedVersionKey]; - } else { - [defaults setObject:lastAuthenticatedVersion - forKey:kBITAuthenticatorLastAuthenticatedVersionKey]; - } -} - -- (NSString *)lastAuthenticatedVersion { - return [[NSUserDefaults standardUserDefaults] objectForKey:kBITAuthenticatorLastAuthenticatedVersionKey]; -} - -- (NSString *)installationIdentifierParameterString { - switch (self.identificationType) { - case BITAuthenticatorIdentificationTypeHockeyAppEmail: - case BITAuthenticatorIdentificationTypeWebAuth: - return @"iuid"; - case BITAuthenticatorIdentificationTypeHockeyAppUser: - return @"auid"; - case BITAuthenticatorIdentificationTypeDevice: - return @"udid"; - case BITAuthenticatorIdentificationTypeAnonymous: - return @"uuid"; - } -} - -+ (NSString *)stringForIdentificationType:(BITAuthenticatorIdentificationType)identificationType { - switch (identificationType) { - case BITAuthenticatorIdentificationTypeHockeyAppEmail: - return @"iuid"; - case BITAuthenticatorIdentificationTypeWebAuth: - return @"webAuth"; - case BITAuthenticatorIdentificationTypeHockeyAppUser: - return @"auid"; - case BITAuthenticatorIdentificationTypeDevice: - return @"udid"; - case BITAuthenticatorIdentificationTypeAnonymous: - return @"uuid"; - } -} - -- (void)setIdentificationType:(BITAuthenticatorIdentificationType)identificationType { - if (_identificationType != identificationType) { - _identificationType = identificationType; - self.identified = NO; - self.validated = NO; - } -} - -- (NSString *)publicInstallationIdentifier { - switch (self.identificationType) { - case BITAuthenticatorIdentificationTypeHockeyAppEmail: - case BITAuthenticatorIdentificationTypeHockeyAppUser: - case BITAuthenticatorIdentificationTypeWebAuth: - return [self stringValueFromKeychainForKey:kBITAuthenticatorUserEmailKey]; - case BITAuthenticatorIdentificationTypeAnonymous: - case BITAuthenticatorIdentificationTypeDevice: - return [self stringValueFromKeychainForKey:kBITAuthenticatorIdentifierKey]; - } -} - -#pragma mark - Application Lifecycle - -- (void)applicationDidBecomeActive:(NSNotification *) __unused note { - [self authenticate]; -} - -- (void)applicationDidEnterBackground:(NSNotification *) __unused note { - if (BITAuthenticatorAppRestrictionEnforcementOnAppActive == self.restrictionEnforcementFrequency) { - self.validated = NO; - } -} - -@end - -#endif /* HOCKEYSDK_FEATURE_AUTHENTICATOR */ diff --git a/submodules/HockeySDK-iOS/Classes/BITAuthenticator_Private.h b/submodules/HockeySDK-iOS/Classes/BITAuthenticator_Private.h deleted file mode 100644 index b0a97ea5b8..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITAuthenticator_Private.h +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Author: Stephan Diederich - * - * Copyright (c) 2013-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_AUTHENTICATOR - -#import "BITAuthenticationViewController.h" - -@class BITHockeyAppClient; - -@interface BITAuthenticator () - -/** - Delegate that can be used to do any last minute configurations on the - presented viewController. - - The delegate is automatically set by using `[BITHockeyManager setDelegate:]`. You - should not need to set this delegate individually. - - @see `[BITHockeyManager setDelegate:]` - @see BITAuthenticatorDelegate - */ -@property (nonatomic, weak) id delegate; - -/** - * must be set - */ -@property (nonatomic, strong) BITHockeyAppClient *hockeyAppClient; - -#pragma mark - -/** - * holds the identifier of the last version that was authenticated - * only used if validation is set BITAuthenticatorValidationTypeOnFirstLaunch - */ -@property (nonatomic, copy) NSString *lastAuthenticatedVersion; - -/** - * returns the type of the string stored in installationIdentifierParameterString - */ -@property (nonatomic, copy, readonly) NSString *installationIdentifierParameterString; - -/** - * returns the string used to identify this app against the HockeyApp backend. - */ -@property (nonatomic, copy, readonly) NSString *installationIdentifier; - -/** - * method registered as observer for applicationDidEnterBackground events - * - * @param note NSNotification - */ -- (void) applicationDidEnterBackground:(NSNotification*) note; - -/** - * method registered as observer for applicationsDidBecomeActive events - * - * @param note NSNotification - */ -- (void) applicationDidBecomeActive:(NSNotification*) note; - -@property (nonatomic, copy) void(^identificationCompletion)(BOOL identified, NSError* error); - -#pragma mark - Overrides -@property (nonatomic, assign, readwrite, getter = isIdentified) BOOL identified; -@property (nonatomic, assign, readwrite, getter = isValidated) BOOL validated; - -#pragma mark - Testing -- (void) storeInstallationIdentifier:(NSString*) identifier withType:(BITAuthenticatorIdentificationType) type; -- (void)validateWithCompletion:(void (^)(BOOL validated, NSError *))completion; -- (void)authenticationViewController:(UIViewController *)viewController - handleAuthenticationWithEmail:(NSString *)email - request:(NSURLRequest *)request - completion:(void (^)(BOOL, NSError *))completion; -- (BOOL) needsValidation; -- (void) authenticate; -@end - -#endif /* HOCKEYSDK_FEATURE_AUTHENTICATOR */ diff --git a/submodules/HockeySDK-iOS/Classes/BITBase.h b/submodules/HockeySDK-iOS/Classes/BITBase.h deleted file mode 100644 index 4cc3cdc0c8..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITBase.h +++ /dev/null @@ -1,13 +0,0 @@ -#import "BITTelemetryObject.h" -#import "BITTelemetryData.h" - -@interface BITBase : BITTelemetryData - -@property (nonatomic, copy) NSString *baseType; - -- (instancetype)initWithCoder:(NSCoder *)coder; - -- (void)encodeWithCoder:(NSCoder *)coder; - - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITBase.m b/submodules/HockeySDK-iOS/Classes/BITBase.m deleted file mode 100644 index efb54b7e26..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITBase.m +++ /dev/null @@ -1,34 +0,0 @@ -#import "BITBase.h" - -/// Data contract class for type Base. -@implementation BITBase - -/// -/// Adds all members of this class to a dictionary -/// @returns dictionary to which the members of this class will be added. -/// -- (NSDictionary *)serializeToDictionary { - NSMutableDictionary *dict = [super serializeToDictionary].mutableCopy; - if (self.baseType != nil) { - [dict setObject:self.baseType forKey:@"baseType"]; - } - return dict; -} - -#pragma mark - NSCoding - -- (instancetype)initWithCoder:(NSCoder *)coder { - self = [super initWithCoder:coder]; - if(self) { - _baseType = [coder decodeObjectForKey:@"self.baseType"]; - } - - return self; -} - -- (void)encodeWithCoder:(NSCoder *)coder { - [super encodeWithCoder:coder]; - [coder encodeObject:self.baseType forKey:@"self.baseType"]; -} - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITBlurImageAnnotation.h b/submodules/HockeySDK-iOS/Classes/BITBlurImageAnnotation.h deleted file mode 100644 index cb5a1f3ecc..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITBlurImageAnnotation.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Author: Moritz Haarmann - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "BITImageAnnotation.h" - -@interface BITBlurImageAnnotation : BITImageAnnotation - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITBlurImageAnnotation.m b/submodules/HockeySDK-iOS/Classes/BITBlurImageAnnotation.m deleted file mode 100644 index 9b1fd3f941..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITBlurImageAnnotation.m +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Author: Moritz Haarmann - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_FEEDBACK - -#import "BITBlurImageAnnotation.h" - -@interface BITBlurImageAnnotation() - -@property (nonatomic, strong) CALayer* imageLayer; -@property (nonatomic, strong) UIImage* scaledImage; -@property (nonatomic, strong) CALayer* selectedLayer; - - -@end - -@implementation BITBlurImageAnnotation - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - self.clipsToBounds = YES; - self.imageLayer = [CALayer layer]; - [self.layer addSublayer:self.imageLayer]; - - self.selectedLayer = [CALayer layer]; - [self.layer insertSublayer:self.selectedLayer above:self.imageLayer]; - - self.selectedLayer.backgroundColor = [[UIColor redColor] colorWithAlphaComponent:0.5].CGColor; - self.selectedLayer.opacity = 0.6f; - self.clipsToBounds = YES; - } - return self; -} - -- (void)setSourceImage:(UIImage *)sourceImage { - CGSize size = CGSizeMake(sourceImage.size.width/30, sourceImage.size.height/30); - - UIGraphicsBeginImageContext(size); - - [sourceImage drawInRect:CGRectMake(0, 0, size.width, size.height)]; - self.scaledImage = UIGraphicsGetImageFromCurrentImageContext(); - self.imageLayer.shouldRasterize = YES; - self.imageLayer.rasterizationScale = 1; - self.imageLayer.magnificationFilter = kCAFilterNearest; - self.imageLayer.contents = (id)self.scaledImage.CGImage; - - UIGraphicsEndImageContext(); -} - -- (void)setSelected:(BOOL)selected { - super.selected = selected; - - if (selected){ - self.selectedLayer.opacity = 0.6f; - } else { - self.selectedLayer.opacity = 0.0f; - } -} - -- (void)layoutSubviews { - [super layoutSubviews]; - - [CATransaction begin]; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wcast-qual" - [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; -#pragma clang diagnostic pop - self.imageLayer.frame = self.imageFrame; - self.imageLayer.masksToBounds = YES; - - self.selectedLayer.frame= self.bounds; - [CATransaction commit]; -} - -- (BOOL)resizable { - return YES; -} - -@end - -#endif /* HOCKEYSDK_FEATURE_FEEDBACK */ diff --git a/submodules/HockeySDK-iOS/Classes/BITCategoryContainer.h b/submodules/HockeySDK-iOS/Classes/BITCategoryContainer.h deleted file mode 100644 index 97b25740cd..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITCategoryContainer.h +++ /dev/null @@ -1,17 +0,0 @@ -#import -#import "HockeySDKFeatureConfig.h" - -#if HOCKEYSDK_FEATURE_METRICS - -#import "HockeySDKNullability.h" -NS_ASSUME_NONNULL_BEGIN - -@interface BITCategoryContainer : NSObject - -+ (void)activateCategory; - -@end - -NS_ASSUME_NONNULL_END - -#endif /* HOCKEYSDK_FEATURE_METRICS */ diff --git a/submodules/HockeySDK-iOS/Classes/BITCategoryContainer.m b/submodules/HockeySDK-iOS/Classes/BITCategoryContainer.m deleted file mode 100644 index 81f23e97f2..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITCategoryContainer.m +++ /dev/null @@ -1,144 +0,0 @@ -#import "BITCategoryContainer.h" -#import "HockeySDKFeatureConfig.h" -#import - -#if HOCKEYSDK_FEATURE_METRICS - -@implementation BITCategoryContainer - -+ (void)activateCategory { - -} - -@end - - -#pragma mark - GZIP library - - -// -// GZIP.m -// -// Version 1.0.3 -// -// Created by Nick Lockwood on 03/06/2012. -// Copyright (C) 2012 Charcoal Design -// -// Distributed under the permissive zlib License -// Get the latest version from here: -// -// https://github.com/nicklockwood/GZIP -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// -// 3. This notice may not be removed or altered from any source distribution. -// - -#import - -static const NSUInteger ChunkSize = 16384; - -@implementation NSData (BITGZIP) - -- (NSData *)bit_gzippedDataWithCompressionLevel:(float)level -{ - if ([self length]) - { - z_stream stream; - stream.zalloc = Z_NULL; - stream.zfree = Z_NULL; - stream.opaque = Z_NULL; - stream.avail_in = (uint)[self length]; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wcast-qual" - stream.next_in = (Bytef *)[self bytes]; -#pragma clang diagnostic pop - stream.total_out = 0; - stream.avail_out = 0; - - int compression = (level < 0.0f)? Z_DEFAULT_COMPRESSION: (int)(roundf(level * 9)); - if (deflateInit2(&stream, compression, Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY) == Z_OK) - { - NSMutableData *data = [NSMutableData dataWithLength:ChunkSize]; - while (stream.avail_out == 0) - { - if (stream.total_out >= [data length]) - { - data.length += ChunkSize; - } - stream.next_out = (uint8_t *)[data mutableBytes] + stream.total_out; - stream.avail_out = (uInt)([data length] - stream.total_out); - deflate(&stream, Z_FINISH); - } - deflateEnd(&stream); - data.length = stream.total_out; - return data; - } - } - return nil; -} - -- (NSData *)bit_gzippedData -{ - return [self bit_gzippedDataWithCompressionLevel:-1.0f]; -} - -- (NSData *)bit_gunzippedData -{ - if ([self length]) - { - z_stream stream; - stream.zalloc = Z_NULL; - stream.zfree = Z_NULL; - stream.avail_in = (uint)[self length]; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wcast-qual" - stream.next_in = (Bytef *)[self bytes]; -#pragma clang diagnostic pop - stream.total_out = 0; - stream.avail_out = 0; - - NSMutableData *data = [NSMutableData dataWithLength:(NSUInteger)([self length] * 1.5)]; - if (inflateInit2(&stream, 47) == Z_OK) - { - int status = Z_OK; - while (status == Z_OK) - { - if (stream.total_out >= [data length]) - { - data.length += [self length] / 2; - } - stream.next_out = (uint8_t *)[data mutableBytes] + stream.total_out; - stream.avail_out = (uInt)([data length] - stream.total_out); - status = inflate (&stream, Z_SYNC_FLUSH); - } - if (inflateEnd(&stream) == Z_OK) - { - if (status == Z_STREAM_END) - { - data.length = stream.total_out; - return data; - } - } - } - } - return nil; -} - -@end - -#endif /* HOCKEYSDK_FEATURE_METRICS */ diff --git a/submodules/HockeySDK-iOS/Classes/BITChannel.h b/submodules/HockeySDK-iOS/Classes/BITChannel.h deleted file mode 100644 index 3e4e5cbe77..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITChannel.h +++ /dev/null @@ -1,47 +0,0 @@ -#import -#import "HockeySDKFeatureConfig.h" - -#if HOCKEYSDK_FEATURE_METRICS - -@class BITConfiguration; -@class BITTelemetryData; -@class BITTelemetryContext; -@class BITPersistence; - -#import "HockeySDKNullability.h" -NS_ASSUME_NONNULL_BEGIN - -/** - * Buffer of telemtry events, will be written to disk. Make sure the buffer is used in a threadsafe way. - */ -FOUNDATION_EXPORT char *_Nullable BITTelemetryEventBuffer; - -/** - * Items get queued before they are persisted and sent out as a batch. This class managed the queue, and forwards the batch - * to the persistence layer once the max batch count has been reached. - */ -@interface BITChannel : NSObject - - -/** - * Initializes a new BITChannel instance. - * - * @param telemetryContext the context used to add context values to the metrics payload - * @param persistence the persistence used to save metrics after the queue gets flushed - * - * @return the telemetry context - */ -- (instancetype)initWithTelemetryContext:(BITTelemetryContext *)telemetryContext persistence:(BITPersistence *) persistence; - -/** - * Reset BITSafeJsonEventsString so we can start appending JSON dictionaries. - * - * @param item The telemetry object, which should be processed - */ -- (void)enqueueTelemetryItem:(BITTelemetryData *)item; - -@end - -NS_ASSUME_NONNULL_END - -#endif /* HOCKEYSDK_FEATURE_METRICS */ diff --git a/submodules/HockeySDK-iOS/Classes/BITChannel.m b/submodules/HockeySDK-iOS/Classes/BITChannel.m deleted file mode 100644 index af43b50b36..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITChannel.m +++ /dev/null @@ -1,505 +0,0 @@ -#import "HockeySDKFeatureConfig.h" - -#if HOCKEYSDK_FEATURE_METRICS - -#import "HockeySDKPrivate.h" -#import "BITHockeyManager.h" -#import "BITChannelPrivate.h" -#import "BITHockeyHelper.h" -#import "BITHockeyHelper+Application.h" -#import "BITTelemetryContext.h" -#import "BITTelemetryData.h" -#import "BITEnvelope.h" -#import "BITData.h" -#import "BITDevice.h" -#import "BITPersistencePrivate.h" -#import "BITSender.h" -#import - - -static char *const BITDataItemsOperationsQueue = "net.hockeyapp.senderQueue"; -char *BITTelemetryEventBuffer; - -NSString *const BITChannelBlockedNotification = @"BITChannelBlockedNotification"; - -static NSInteger const BITDefaultMaxBatchSize = 50; -static NSInteger const BITDefaultBatchInterval = 15; -static NSInteger const BITSchemaVersion = 2; - -static NSInteger const BITDebugMaxBatchSize = 5; -static NSInteger const BITDebugBatchInterval = 3; - -typedef _Atomic(char*) atomic_charptr; - -NS_ASSUME_NONNULL_BEGIN - -@interface BITChannel () - -@property (nonatomic, weak, nullable) id appDidEnterBackgroundObserver; - -@end - -@implementation BITChannel - -@synthesize persistence = _persistence; -@synthesize channelBlocked = _channelBlocked; - -#pragma mark - Initialisation - -- (instancetype)init { - if ((self = [super init])) { - bit_resetEventBuffer(&BITTelemetryEventBuffer); - _dataItemCount = 0; - if (bit_isDebuggerAttached()) { - _maxBatchSize = BITDebugMaxBatchSize; - _batchInterval = BITDebugBatchInterval; - } else { - _maxBatchSize = BITDefaultMaxBatchSize; - _batchInterval = BITDefaultBatchInterval; - } - dispatch_queue_t serialQueue = dispatch_queue_create(BITDataItemsOperationsQueue, DISPATCH_QUEUE_SERIAL); - _dataItemsOperations = serialQueue; - - [self registerObservers]; - } - return self; -} - -- (instancetype)initWithTelemetryContext:(BITTelemetryContext *)telemetryContext persistence:(BITPersistence *)persistence { - if ((self = [self init])) { - _telemetryContext = telemetryContext; - _persistence = persistence; - } - return self; -} - -- (void)dealloc { - [self unregisterObservers]; - [self invalidateTimer]; -} - -#pragma mark - Observers - -- (void) registerObservers { - NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; - __weak typeof(self) weakSelf = self; - - if (nil == self.appDidEnterBackgroundObserver) { - void (^notificationBlock)(NSNotification *note) = ^(NSNotification __unused *note) { - typeof(self) strongSelf = weakSelf; - if ([strongSelf timerIsRunning]) { - - /** - * From the documentation for applicationDidEnterBackground: - * It's likely any background tasks you start in applicationDidEnterBackground: will not run until after that method exits, - * you should request additional background execution time before starting those tasks. In other words, - * first call beginBackgroundTaskWithExpirationHandler: and then run the task on a dispatch queue or secondary thread. - */ - UIApplication *application = [UIApplication sharedApplication]; - [strongSelf persistDataItemQueueWithBackgroundTask: application]; - } - }; - self.appDidEnterBackgroundObserver = [center addObserverForName:UIApplicationDidEnterBackgroundNotification - object:nil - queue:NSOperationQueue.mainQueue - usingBlock:notificationBlock]; - } -} - -- (void) unregisterObservers { - NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; - id appDidEnterBackgroundObserver = self.appDidEnterBackgroundObserver; - if (appDidEnterBackgroundObserver) { - [center removeObserver:appDidEnterBackgroundObserver]; - self.appDidEnterBackgroundObserver = nil; - } -} - -#pragma mark - Queue management - -- (BOOL)isQueueBusy { - if (!self.channelBlocked) { - BOOL persistenceBusy = ![self.persistence isFreeSpaceAvailable]; - if (persistenceBusy) { - self.channelBlocked = YES; - [self sendBlockingChannelNotification]; - } - } - return self.channelBlocked; -} - -- (void)persistDataItemQueue:(char **)eventBuffer { - [self invalidateTimer]; - - // Make sure string (which points to BITTelemetryEventBuffer) is not changed. - char *previousBuffer = NULL; - char *newEmptyString = strdup(""); - do { - previousBuffer = *eventBuffer; - - // This swaps pointers and makes sure eventBuffer now has the balue of newEmptyString. - if (atomic_compare_exchange_strong((atomic_charptr *)eventBuffer, &previousBuffer, newEmptyString)) { - @synchronized(self) { - self.dataItemCount = 0; - } - break; - } - } while(true); - - // Nothing to persist, freeing memory and existing. - if (!previousBuffer || strlen(previousBuffer) == 0) { - free(previousBuffer); - return; - } - - // Persist the data - NSData *bundle = [NSData dataWithBytes:previousBuffer length:strlen(previousBuffer)]; - [self.persistence persistBundle:bundle]; - free(previousBuffer); - - // Reset both, the async-signal-safe and item counter. - [self resetQueue]; -} - -- (void)persistDataItemQueueWithBackgroundTask:(UIApplication *)application { - __weak typeof(self) weakSelf = self; - dispatch_async(self.dataItemsOperations, ^{ - typeof(self) strongSelf = weakSelf; - [strongSelf persistDataItemQueue:&BITTelemetryEventBuffer]; - }); - [self createBackgroundTaskWhileDataIsSending:application withWaitingGroup:nil]; -} - -- (void)createBackgroundTaskWhileDataIsSending:(UIApplication *)application - withWaitingGroup:(nullable dispatch_group_t)group { - if (application == nil) { - return; - } - - // Queues needs for waiting consistently. - NSArray *queues = @[ - self.dataItemsOperations, // For enqueue - self.persistence.persistenceQueue, // For persist - dispatch_get_main_queue() // For notification - ]; - - // Tracking for sender activity. - // BITPersistenceSuccessNotification - start sending - // BITSenderFinishSendingDataNotification - finish sending - __block dispatch_group_t senderGroup = dispatch_group_create(); - __block NSInteger senderCounter = 0; - __block id persistenceSuccessObserver = [[NSNotificationCenter defaultCenter] - addObserverForName:BITPersistenceSuccessNotification - object:nil - queue:nil - usingBlock:^(__unused NSNotification *notification) { - dispatch_group_enter(senderGroup); - senderCounter++; - if (persistenceSuccessObserver) { - [[NSNotificationCenter defaultCenter] removeObserver:persistenceSuccessObserver]; - persistenceSuccessObserver = nil; - } - }]; - __block id senderFinishSendingDataObserver = [[NSNotificationCenter defaultCenter] - addObserverForName:BITSenderFinishSendingDataNotification - object:nil - queue:nil - usingBlock:^(__unused NSNotification *notification) { - if (senderCounter > 0) { - dispatch_group_leave(senderGroup); - senderCounter--; - } - if (senderFinishSendingDataObserver) { - [[NSNotificationCenter defaultCenter] removeObserver:senderFinishSendingDataObserver]; - senderFinishSendingDataObserver = nil; - } - }]; - - BITHockeyLogVerbose(@"BITChannel: Start background task"); - __block UIBackgroundTaskIdentifier backgroundTask = [application beginBackgroundTaskWithExpirationHandler:^{ - BITHockeyLogVerbose(@"BITChannel: Background task is expired"); - [application endBackgroundTask:backgroundTask]; - backgroundTask = UIBackgroundTaskInvalid; - }]; - __block NSUInteger i = 0; - __block __weak void (^weakWaitBlock)(void); - void (^waitBlock)(void); - weakWaitBlock = waitBlock = ^{ - if (i < queues.count) { - dispatch_queue_t queue = [queues objectAtIndex:i++]; - BITHockeyLogVerbose(@"BITChannel: Waiting queue: %@", [[NSString alloc] initWithUTF8String:dispatch_queue_get_label(queue)]); - dispatch_async(queue, weakWaitBlock); - } else { - BITHockeyLogVerbose(@"BITChannel: Waiting sender"); - dispatch_group_notify(senderGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - if (backgroundTask != UIBackgroundTaskInvalid) { - BITHockeyLogVerbose(@"BITChannel: Cancel background task"); - [application endBackgroundTask:backgroundTask]; - backgroundTask = UIBackgroundTaskInvalid; - } - }); - } - }; - if (group != nil) { - BITHockeyLogVerbose(@"BITChannel: Waiting group"); - dispatch_group_notify((dispatch_group_t)group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), waitBlock); - } else { - waitBlock(); - } -} - -// Resets the event buffer and count of events in the queue. -- (void)resetQueue { - @synchronized (self) { - bit_resetEventBuffer(&BITTelemetryEventBuffer); - self.dataItemCount = 0; - } -} - -#pragma mark - Adding to queue - -- (void)enqueueTelemetryItem:(BITTelemetryData *)item { - [self enqueueTelemetryItem:item completionHandler:nil]; -} - -- (void)enqueueTelemetryItem:(BITTelemetryData *)item completionHandler:(nullable void (^)(void))completionHandler { - if (!item) { - - // Item is nil: Do not enqueue item and abort operation. - BITHockeyLogWarning(@"WARNING: TelemetryItem was nil."); - if(completionHandler) { - completionHandler(); - } - return; - } - - // First assigning self to weakSelf and then assigning this to strongSelf in the block is not very intuitive, this - // blog post explains it very well: https://dhoerl.wordpress.com/2013/04/23/i-finally-figured-out-weakself-and-strongself/ - __weak typeof(self) weakSelf = self; - dispatch_async(self.dataItemsOperations, ^{ - - typeof(self) strongSelf = weakSelf; - if (strongSelf.isQueueBusy) { - - // Case 1: Channel is in blocked state: Trigger sender, start timer to check after again after a while and abort operation. - BITHockeyLogDebug(@"INFO: The channel is saturated. %@ was dropped.", item.debugDescription); - if (![strongSelf timerIsRunning]) { - [strongSelf startTimer]; - } - - if(completionHandler) { - completionHandler(); - } - - return; - } - - // Should be outside of @synchronized block! - BOOL applicationIsInBackground = ([BITHockeyHelper applicationState] == BITApplicationStateBackground); - - // Enqueue item. - @synchronized(self) { - NSDictionary *dict = [strongSelf dictionaryForTelemetryData:item]; - [strongSelf appendDictionaryToEventBuffer:dict]; - - // If the app is running in the background. - if (strongSelf.dataItemCount >= strongSelf.maxBatchSize || applicationIsInBackground) { - - // Case 2: Max batch count has been reached or the app is running in the background, so write queue to disk and delete all items. - [strongSelf persistDataItemQueue:&BITTelemetryEventBuffer]; - } else if (strongSelf.dataItemCount > 0) { - - // Case 3: It is the first item, let's start the timer. - if (![strongSelf timerIsRunning]) { - [strongSelf startTimer]; - } - } - - if(completionHandler) { - completionHandler(); - } - } - }); -} - -#pragma mark - Envelope telemerty items - -- (NSDictionary *)dictionaryForTelemetryData:(BITTelemetryData *) telemetryData { - - BITEnvelope *envelope = [self envelopeForTelemetryData:telemetryData]; - NSDictionary *dict = [envelope serializeToDictionary]; - return dict; -} - -- (BITEnvelope *)envelopeForTelemetryData:(BITTelemetryData *)telemetryData { - telemetryData.version = @(BITSchemaVersion); - - BITData *data = [BITData new]; - data.baseData = telemetryData; - data.baseType = telemetryData.dataTypeName; - - BITEnvelope *envelope = [BITEnvelope new]; - envelope.time = bit_utcDateString([NSDate date]); - envelope.iKey = self.telemetryContext.appIdentifier; - - envelope.tags = self.telemetryContext.contextDictionary; - envelope.data = data; - envelope.name = telemetryData.envelopeTypeName; - - return envelope; -} - -#pragma mark - Serialization Helper - -- (NSString *)serializeDictionaryToJSONString:(NSDictionary *)dictionary { - NSError *error; - NSData *data = [NSJSONSerialization dataWithJSONObject:dictionary options:(NSJSONWritingOptions)0 error:&error]; - if (!data) { - BITHockeyLogError(@"ERROR: JSONSerialization error: %@", error.localizedDescription); - return @"{}"; - } else { - return (NSString *)[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - } -} - -#pragma mark JSON Stream - -- (void)appendDictionaryToEventBuffer:(NSDictionary *)dictionary { - if (dictionary) { - NSString *string = [self serializeDictionaryToJSONString:dictionary]; - - // Since we can't persist every event right away, we write it to a simple C string. - // This can then be written to disk by a signal handler in case of a crash. - @synchronized (self) { - bit_appendStringToEventBuffer(string, &BITTelemetryEventBuffer); - self.dataItemCount += 1; - } - - BITHockeyLogVerbose(@"VERBOSE: Appended data to buffer:\n%@", string); - } -} - -void bit_appendStringToEventBuffer(NSString *string, char **eventBuffer) { - if (eventBuffer == NULL) { - return; - } - - if (!string) { - return; - } - - if (*eventBuffer == NULL || strlen(*eventBuffer) == 0) { - bit_resetEventBuffer(eventBuffer); - } - - if (string.length == 0) { - return; - } - - do { - char *newBuffer = NULL; - char *previousBuffer = *eventBuffer; - - // Concatenate old string with new JSON string and add a comma. - asprintf(&newBuffer, "%s%.*s\n", previousBuffer, (int)MIN(string.length, (NSUInteger)INT_MAX), string.UTF8String); - - // Compare newBuffer and previousBuffer. If they point to the same address, we are safe to use them. - if (atomic_compare_exchange_strong((atomic_charptr *)eventBuffer, &previousBuffer, newBuffer)) { - - // Free the intermediate pointer. - free(previousBuffer); - return; - } else { - - // newBuffer has been changed by another thread. - free(newBuffer); - } - } while (true); -} - -void bit_resetEventBuffer(char **eventBuffer) { - if (!eventBuffer) { - return; - } - - char *prevString = NULL; - char *newEmptyString = strdup(""); - do { - prevString = *eventBuffer; - - // Compare pointers to strings to make sure we are still threadsafe! - if (atomic_compare_exchange_strong((atomic_charptr *)eventBuffer, &prevString, newEmptyString)) { - free(prevString); - return; - } - } while(true); -} - -#pragma mark - Batching - -- (NSUInteger)maxBatchSize { - if(_maxBatchSize <= 0){ - return BITDefaultMaxBatchSize; - } - return _maxBatchSize; -} - -- (void)invalidateTimer { - @synchronized(self) { - if (self.timerSource != nil) { - dispatch_source_cancel((dispatch_source_t)self.timerSource); - self.timerSource = nil; - } - } -} - --(BOOL)timerIsRunning { - @synchronized(self) { - return self.timerSource != nil; - } -} - -- (void)startTimer { - @synchronized(self) { - - // Reset timer, if it is already running. - [self invalidateTimer]; - - dispatch_source_t timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.dataItemsOperations); - dispatch_source_set_timer(timerSource, dispatch_walltime(NULL, NSEC_PER_SEC * self.batchInterval), 1ull * NSEC_PER_SEC, 1ull * NSEC_PER_SEC); - __weak typeof(self) weakSelf = self; - dispatch_source_set_event_handler(timerSource, ^{ - typeof(self) strongSelf = weakSelf; - if (strongSelf) { - if (strongSelf.dataItemCount > 0) { - [strongSelf persistDataItemQueue:&BITTelemetryEventBuffer]; - } else { - strongSelf.channelBlocked = NO; - } - [strongSelf invalidateTimer]; - } - }); - dispatch_resume(timerSource); - self.timerSource = timerSource; - } -} - -/** - * Send a BITHockeyBlockingChannelNotification to the main thread to notify observers that channel can't enqueue new items. - * This is typically used to trigger sending. - */ -- (void)sendBlockingChannelNotification { - dispatch_async(dispatch_get_main_queue(), ^{ - BITHockeyLogDebug(@"Sending notification: %@", BITChannelBlockedNotification); - [[NSNotificationCenter defaultCenter] postNotificationName:BITChannelBlockedNotification - object:nil - userInfo:nil]; - }); -} - -@end - -NS_ASSUME_NONNULL_END - -#endif /* HOCKEYSDK_FEATURE_METRICS */ - diff --git a/submodules/HockeySDK-iOS/Classes/BITChannelPrivate.h b/submodules/HockeySDK-iOS/Classes/BITChannelPrivate.h deleted file mode 100644 index 97c836f709..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITChannelPrivate.h +++ /dev/null @@ -1,125 +0,0 @@ -#import -#import -#import "HockeySDKFeatureConfig.h" - -#if HOCKEYSDK_FEATURE_METRICS - -@class BITTelemetryData; -@class BITTelemetryContext; -@class BITPersistence; - -#import "BITChannel.h" - -#import "HockeySDKNullability.h" -NS_ASSUME_NONNULL_BEGIN - -@interface BITChannel () - -/** - * Notification that will be send on the main thread to notifiy observers that channel can't enqueue new items. - * This is typically used to trigger sending to the server. - */ -FOUNDATION_EXPORT NSString *const BITChannelBlockedNotification; - -/** - * Telemetry context used by the channel to create the payload (testing). - */ -@property (nonatomic, strong) BITTelemetryContext *telemetryContext; - -/** - * Persistence instance for storing files after the queue gets flushed (testing). - */ -@property (nonatomic, strong) BITPersistence *persistence; - -/* - * Threshold for sending data to the server. Default batch size for debugging is 150, for release - * configuration, the batch size is 5. - * - * Default: 50 - * - * @warning: We advice to not set the batch size below 5 events. - */ -@property (nonatomic) NSUInteger maxBatchSize; - -/* - * Interval for sending data to the server in seconds. - * - * Default: 15 - */ -@property (nonatomic, assign) NSInteger batchInterval; - -/** - * A timer source which is used to flush the queue after a cretain time. - */ -@property (nonatomic, strong, nullable) dispatch_source_t timerSource; - -/** - * A queue which makes array operations thread safe. - */ -@property (nonatomic, strong) dispatch_queue_t dataItemsOperations; - -/** - * An integer value that keeps tracks of the number of data items added to the JSON Stream string. - */ -@property (nonatomic, assign) NSUInteger dataItemCount; - -/** - * Indicates that channel is currently in a blocked state. - */ -@property BOOL channelBlocked; - -/** - * Manually trigger the BITChannel to persist all items currently in its data item queue. - */ -- (void)persistDataItemQueue:(char *_Nullable*_Nullable)eventBuffer; - -/** - * Create background task for queues and group. - */ -- (void)createBackgroundTaskWhileDataIsSending:(UIApplication *)application - withWaitingGroup:(nullable dispatch_group_t)group; - -/** - * Adds the specified dictionary to the JSON Stream string. - * - * @param dictionary the dictionary object which is to be added to the JSON Stream queue string. - */ -- (void)appendDictionaryToEventBuffer:(NSDictionary *)dictionary; - -/** - * A C function that serializes a given dictionary to JSON and appends it to a char string - * - * @param string The C string which the dictionary's JSON representation will be appended to. - */ -void bit_appendStringToEventBuffer(NSString *string, char *__nonnull*__nonnull eventBuffer); - -/** - * Reset the event buffer so we can start appending JSON dictionaries. - * - * @param eventBuffer The string that will be reset. - */ -void bit_resetEventBuffer(char *__nonnull*__nonnull eventBuffer); - -/** - * A method which indicates whether the telemetry pipeline is busy and no new data should be enqueued. - * Currently, we drop telemetry data if this returns YES. - * This depends on defaultMaxBatchCount and defaultBatchInterval. - * - * @return Returns yes if currently no new data should be enqueued on the channel. - */ -- (BOOL)isQueueBusy; - -/** - * Enqueue a telemetry item. This is for testing purposes where we actually use the completion handler. - * - * @param completionHandler The completion handler that will be called after enqueuing a BITTelemetryData object. - * - * @discussion intended for testing purposes. - */ -- (void)enqueueTelemetryItem:(BITTelemetryData *)item completionHandler:(nullable void (^)(void))completionHandler; - -@end - -NS_ASSUME_NONNULL_END - -#endif /* HOCKEYSDK_FEATURE_METRICS */ diff --git a/submodules/HockeySDK-iOS/Classes/BITCrashAttachment.h b/submodules/HockeySDK-iOS/Classes/BITCrashAttachment.h deleted file mode 100644 index 9f7d4273c0..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITCrashAttachment.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "BITHockeyAttachment.h" - -/** - Deprecated: Provides support to add binary attachments to crash reports - - This class is not needed any longer and exists for compatibility purposes with - HockeySDK-iOS 3.5.5. - - It is a subclass of `BITHockeyAttachment` which only provides an initializer - that is compatible with the one of HockeySDK-iOS 3.5.5. - - This is used by `[BITCrashManagerDelegate attachmentForCrashManager:]` - - @see BITHockeyAttachment - */ -@interface BITCrashAttachment : BITHockeyAttachment - -/** - Create an BITCrashAttachment instance with a given filename and NSData object - - @param filename The filename the attachment should get - @param crashAttachmentData The attachment data as NSData - @param contentType The content type of your data as MIME type - - @return An instance of BITCrashAttachment - */ -- (instancetype)initWithFilename:(NSString *)filename - crashAttachmentData:(NSData *)crashAttachmentData - contentType:(NSString *)contentType; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITCrashAttachment.m b/submodules/HockeySDK-iOS/Classes/BITCrashAttachment.m deleted file mode 100644 index f4f408d25d..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITCrashAttachment.m +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_CRASH_REPORTER - -#import "BITCrashAttachment.h" - -@implementation BITCrashAttachment - -- (instancetype)initWithFilename:(NSString *)filename - crashAttachmentData:(NSData *)crashAttachmentData - contentType:(NSString *)contentType -{ - self = [super initWithFilename:filename hockeyAttachmentData:crashAttachmentData contentType:contentType]; - - return self; -} - -@end - -#endif diff --git a/submodules/HockeySDK-iOS/Classes/BITCrashCXXExceptionHandler.h b/submodules/HockeySDK-iOS/Classes/BITCrashCXXExceptionHandler.h deleted file mode 100644 index 9efc2d2292..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITCrashCXXExceptionHandler.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Author: Gwynne Raskind - * - * Copyright (c) 2015 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import -#import "HockeySDKNullability.h" - -typedef struct { - const void * __nullable exception; - const char * __nullable exception_type_name; - const char * __nullable exception_message; - uint32_t exception_frames_count; - const uintptr_t * __nonnull exception_frames; -} BITCrashUncaughtCXXExceptionInfo; - -typedef void (*BITCrashUncaughtCXXExceptionHandler)( - const BITCrashUncaughtCXXExceptionInfo * __nonnull info -); - -@interface BITCrashUncaughtCXXExceptionHandlerManager : NSObject - -+ (void)addCXXExceptionHandler:(nonnull BITCrashUncaughtCXXExceptionHandler)handler; -+ (void)removeCXXExceptionHandler:(nonnull BITCrashUncaughtCXXExceptionHandler)handler; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITCrashCXXExceptionHandler.mm b/submodules/HockeySDK-iOS/Classes/BITCrashCXXExceptionHandler.mm deleted file mode 100644 index 233ab5ea27..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITCrashCXXExceptionHandler.mm +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Author: Gwynne Raskind - * - * Copyright (c) 2015 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_CRASH_REPORTER - -#import "BITCrashCXXExceptionHandler.h" -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -typedef std::vector BITCrashUncaughtCXXExceptionHandlerList; -typedef struct -{ - void *exception_object; - uintptr_t call_stack[128]; - uint32_t num_frames; -} BITCrashCXXExceptionTSInfo; - -static bool _BITCrashIsOurTerminateHandlerInstalled = false; -static std::terminate_handler _BITCrashOriginalTerminateHandler = nullptr; -static BITCrashUncaughtCXXExceptionHandlerList _BITCrashUncaughtExceptionHandlerList; -// We are ignoring warnings about OSSpinLock being deprecated because a replacement API -// for this was introduced only in iOS 10.0. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" -static OSSpinLock _BITCrashCXXExceptionHandlingLock = OS_SPINLOCK_INIT; -#pragma clang diagnostic pop -static pthread_key_t _BITCrashCXXExceptionInfoTSDKey = 0; - -@implementation BITCrashUncaughtCXXExceptionHandlerManager - -extern "C" void __attribute__((noreturn)) __cxa_throw(void *exception_object, std::type_info *tinfo, void (*dest)(void *)) -{ - // Purposely do not take a lock in this function. The aim is to be as fast as - // possible. While we could really use some of the info set up by the real - // __cxa_throw, if we call through we never get control back - the function is - // noreturn and jumps to landing pads. Most of the stuff in __cxxabiv1 also - // won't work yet. We therefore have to do these checks by hand. - - // The technique for distinguishing Objective-C exceptions is based on the - // implementation of objc_exception_throw(). It's weird, but it's fast. The - // explicit symbol load and NULL checks should guard against the - // implementation changing in a future version. (Or not existing in an earlier - // version). - - typedef void (*cxa_throw_func)(void *, std::type_info *, void (*)(void *)) __attribute__((noreturn)); - static dispatch_once_t predicate = 0; - static cxa_throw_func __original__cxa_throw = nullptr; - static const void **__real_objc_ehtype_vtable = nullptr; - - dispatch_once(&predicate, ^ { - __original__cxa_throw = reinterpret_cast(dlsym(RTLD_NEXT, "__cxa_throw")); - __real_objc_ehtype_vtable = reinterpret_cast(dlsym(RTLD_DEFAULT, "objc_ehtype_vtable")); - }); - - // Actually check for Objective-C exceptions. - if (tinfo && __real_objc_ehtype_vtable && // Guard from an ABI change - *reinterpret_cast(tinfo) == __real_objc_ehtype_vtable + 2) { - goto callthrough; - } - - // Any other exception that came here has to be C++, since Objective-C is the - // only (known) runtime that hijacks the C++ ABI this way. We need to save off - // a backtrace. - // Invariant: If the terminate handler is installed, the TSD key must also be - // initialized. - if (_BITCrashIsOurTerminateHandlerInstalled) { - BITCrashCXXExceptionTSInfo *info = static_cast(pthread_getspecific(_BITCrashCXXExceptionInfoTSDKey)); - - if (!info) { - info = reinterpret_cast(calloc(1, sizeof(BITCrashCXXExceptionTSInfo))); - pthread_setspecific(_BITCrashCXXExceptionInfoTSDKey, info); - } - info->exception_object = exception_object; - // XXX: All significant time in this call is spent right here. - info->num_frames = backtrace(reinterpret_cast(&info->call_stack[0]), sizeof(info->call_stack) / sizeof(info->call_stack[0])); - } - -callthrough: - if (__original__cxa_throw) { - __original__cxa_throw(exception_object, tinfo, dest); - } else { - abort(); - } -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunreachable-code" - __builtin_unreachable(); -#pragma clang diagnostic pop -} - -__attribute__((always_inline)) -static inline void BITCrashIterateExceptionHandlers_unlocked(const BITCrashUncaughtCXXExceptionInfo &info) -{ - for (const auto &handler : _BITCrashUncaughtExceptionHandlerList) { - handler(&info); - } -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" -static void BITCrashUncaughtCXXTerminateHandler(void) -{ - BITCrashUncaughtCXXExceptionInfo info = { - .exception = nullptr, - .exception_type_name = nullptr, - .exception_message = nullptr, - .exception_frames_count = 0, - .exception_frames = nullptr, - }; - auto p = std::current_exception(); - - OSSpinLockLock(&_BITCrashCXXExceptionHandlingLock); { - if (p) { // explicit operator bool - info.exception = reinterpret_cast(&p); - info.exception_type_name = __cxxabiv1::__cxa_current_exception_type()->name(); - - BITCrashCXXExceptionTSInfo *recorded_info = reinterpret_cast(pthread_getspecific(_BITCrashCXXExceptionInfoTSDKey)); - - if (recorded_info) { - info.exception_frames_count = recorded_info->num_frames - 1; - info.exception_frames = &recorded_info->call_stack[1]; - } else { - // There's no backtrace, grab this function's trace instead. Probably - // means the exception came from a dynamically loaded library. - void *frames[128] = { nullptr }; - - info.exception_frames_count = backtrace(&frames[0], sizeof(frames) / sizeof(frames[0])) - 1; - info.exception_frames = reinterpret_cast(&frames[1]); - } - - try { - std::rethrow_exception(p); - } catch (const std::exception &e) { // C++ exception. - info.exception_message = e.what(); - BITCrashIterateExceptionHandlers_unlocked(info); - } catch (const std::exception *e) { // C++ exception by pointer. - info.exception_message = e->what(); - BITCrashIterateExceptionHandlers_unlocked(info); - } catch (const std::string &e) { // C++ string as exception. - info.exception_message = e.c_str(); - BITCrashIterateExceptionHandlers_unlocked(info); - } catch (const std::string *e) { // C++ string pointer as exception. - info.exception_message = e->c_str(); - BITCrashIterateExceptionHandlers_unlocked(info); - } catch (const char *e) { // Plain string as exception. - info.exception_message = e; - BITCrashIterateExceptionHandlers_unlocked(info); - } catch (id __unused e) { // Objective-C exception. Pass it on to Foundation. - OSSpinLockUnlock(&_BITCrashCXXExceptionHandlingLock); - if (_BITCrashOriginalTerminateHandler != nullptr) { - _BITCrashOriginalTerminateHandler(); - } - return; - } catch (...) { // Any other kind of exception. No message. - BITCrashIterateExceptionHandlers_unlocked(info); - } - } - } OSSpinLockUnlock(&_BITCrashCXXExceptionHandlingLock); // In case terminate is called reentrantly by pasing it on - - if (_BITCrashOriginalTerminateHandler != nullptr) { - _BITCrashOriginalTerminateHandler(); - } else { - abort(); - } -} - -+ (void)addCXXExceptionHandler:(BITCrashUncaughtCXXExceptionHandler)handler -{ - static dispatch_once_t key_predicate = 0; - - // This only EVER has to be done once, since we don't delete the TSD later - // (there's no reason to delete it). - dispatch_once(&key_predicate, ^ { - pthread_key_create(&_BITCrashCXXExceptionInfoTSDKey, free); - }); - - OSSpinLockLock(&_BITCrashCXXExceptionHandlingLock); { - if (!_BITCrashIsOurTerminateHandlerInstalled) { - _BITCrashOriginalTerminateHandler = std::set_terminate(BITCrashUncaughtCXXTerminateHandler); - _BITCrashIsOurTerminateHandlerInstalled = true; - } - _BITCrashUncaughtExceptionHandlerList.push_back(handler); - } OSSpinLockUnlock(&_BITCrashCXXExceptionHandlingLock); -} - -+ (void)removeCXXExceptionHandler:(BITCrashUncaughtCXXExceptionHandler)handler -{ - OSSpinLockLock(&_BITCrashCXXExceptionHandlingLock); { - auto i = std::find(_BITCrashUncaughtExceptionHandlerList.begin(), _BITCrashUncaughtExceptionHandlerList.end(), handler); - - if (i != _BITCrashUncaughtExceptionHandlerList.end()) { - _BITCrashUncaughtExceptionHandlerList.erase(i); - } - - if (_BITCrashIsOurTerminateHandlerInstalled) { - if (_BITCrashUncaughtExceptionHandlerList.empty()) { - std::terminate_handler previous_handler = std::set_terminate(_BITCrashOriginalTerminateHandler); - - if (previous_handler != BITCrashUncaughtCXXTerminateHandler) { - std::set_terminate(previous_handler); - } else { - _BITCrashIsOurTerminateHandlerInstalled = false; - _BITCrashOriginalTerminateHandler = nullptr; - } - } - } - } OSSpinLockUnlock(&_BITCrashCXXExceptionHandlingLock); -} -#pragma clang diagnostic pop - -@end - -#endif /* HOCKEYSDK_FEATURE_CRASH_REPORTER */ diff --git a/submodules/HockeySDK-iOS/Classes/BITCrashDetails.h b/submodules/HockeySDK-iOS/Classes/BITCrashDetails.h deleted file mode 100644 index be2d62fa3a..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITCrashDetails.h +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import - -/** - * Provides details about the crash that occurred in the previous app session - */ -@interface BITCrashDetails : NSObject - -/** - * UUID for the crash report - */ -@property (nonatomic, readonly, copy) NSString *incidentIdentifier; - -/** - * UUID for the app installation on the device - */ -@property (nonatomic, readonly, copy) NSString *reporterKey; - -/** - * Signal that caused the crash - */ -@property (nonatomic, readonly, copy) NSString *signal; - -/** - * Exception name that triggered the crash, nil if the crash was not caused by an exception - */ -@property (nonatomic, readonly, copy) NSString *exceptionName; - -/** - * Exception reason, nil if the crash was not caused by an exception - */ -@property (nonatomic, readonly, copy) NSString *exceptionReason; - -/** - * Date and time the app started, nil if unknown - */ -@property (nonatomic, readonly, strong) NSDate *appStartTime; - -/** - * Date and time the crash occurred, nil if unknown - */ -@property (nonatomic, readonly, strong) NSDate *crashTime; - -/** - * Operation System version string the app was running on when it crashed. - */ -@property (nonatomic, readonly, copy) NSString *osVersion; - -/** - * Operation System build string the app was running on when it crashed - * - * This may be unavailable. - */ -@property (nonatomic, readonly, copy) NSString *osBuild; - -/** - * CFBundleShortVersionString value of the app that crashed - * - * Can be `nil` if the crash was captured with an older version of the SDK - * or if the app doesn't set the value. - */ -@property (nonatomic, readonly, copy) NSString *appVersion; - -/** - * CFBundleVersion value of the app that crashed - */ -@property (nonatomic, readonly, copy) NSString *appBuild; - -/** - * Identifier of the app process that crashed - */ -@property (nonatomic, readonly, assign) NSUInteger appProcessIdentifier; - -/** - Indicates if the app was killed while being in foreground from the iOS - - If `[BITCrashManager enableAppNotTerminatingCleanlyDetection]` is enabled, use this on startup - to check if the app starts the first time after it was killed by iOS in the previous session. - - This can happen if it consumed too much memory or the watchdog killed the app because it - took too long to startup or blocks the main thread for too long, or other reasons. See Apple - documentation: https://developer.apple.com/library/ios/qa/qa1693/_index.html - - See `[BITCrashManager enableAppNotTerminatingCleanlyDetection]` for more details about which kind of kills can be detected. - - @warning This property only has a correct value, once `[BITHockeyManager startManager]` was - invoked! In addition, it is automatically disabled while a debugger session is active! - - @see `[BITCrashManager enableAppNotTerminatingCleanlyDetection]` - @see `[BITCrashManager didReceiveMemoryWarningInLastSession]` - - @return YES if the details represent an app kill instead of a crash - */ -- (BOOL)isAppKill; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITCrashDetails.m b/submodules/HockeySDK-iOS/Classes/BITCrashDetails.m deleted file mode 100644 index b9fa38f6bb..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITCrashDetails.m +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_CRASH_REPORTER - -#import "BITCrashDetails.h" -#import "BITCrashDetailsPrivate.h" - -NSString *const kBITCrashKillSignal = @"SIGKILL"; - -@implementation BITCrashDetails - -- (instancetype)initWithIncidentIdentifier:(NSString *)incidentIdentifier - reporterKey:(NSString *)reporterKey - signal:(NSString *)signal - exceptionName:(NSString *)exceptionName - exceptionReason:(NSString *)exceptionReason - appStartTime:(NSDate *)appStartTime - crashTime:(NSDate *)crashTime - osVersion:(NSString *)osVersion - osBuild:(NSString *)osBuild - appVersion:(NSString *)appVersion - appBuild:(NSString *)appBuild - appProcessIdentifier:(NSUInteger)appProcessIdentifier -{ - if ((self = [super init])) { - _incidentIdentifier = incidentIdentifier; - _reporterKey = reporterKey; - _signal = signal; - _exceptionName = exceptionName; - _exceptionReason = exceptionReason; - _appStartTime = appStartTime; - _crashTime = crashTime; - _osVersion = osVersion; - _osBuild = osBuild; - _appVersion = appVersion; - _appBuild = appBuild; - _appProcessIdentifier = appProcessIdentifier; - } - return self; -} - -- (BOOL)isAppKill { - BOOL result = NO; - - if (self.signal && [[self.signal uppercaseString] isEqualToString:kBITCrashKillSignal]) - result = YES; - - return result; -} - -@end - -#endif /* HOCKEYSDK_FEATURE_CRASH_REPORTER */ diff --git a/submodules/HockeySDK-iOS/Classes/BITCrashDetailsPrivate.h b/submodules/HockeySDK-iOS/Classes/BITCrashDetailsPrivate.h deleted file mode 100644 index 48b3061344..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITCrashDetailsPrivate.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -extern NSString *const kBITCrashKillSignal; - -@interface BITCrashDetails () { - -} - -- (instancetype)initWithIncidentIdentifier:(NSString *)incidentIdentifier - reporterKey:(NSString *)reporterKey - signal:(NSString *)signal - exceptionName:(NSString *)exceptionName - exceptionReason:(NSString *)exceptionReason - appStartTime:(NSDate *)appStartTime - crashTime:(NSDate *)crashTime - osVersion:(NSString *)osVersion - osBuild:(NSString *)osBuild - appVersion:(NSString *)appVersion - appBuild:(NSString *)appBuild - appProcessIdentifier:(NSUInteger)appProcessIdentifier; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITCrashManager.h b/submodules/HockeySDK-iOS/Classes/BITCrashManager.h deleted file mode 100644 index 69e7500f80..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITCrashManager.h +++ /dev/null @@ -1,441 +0,0 @@ -/* - * Author: Andreas Linde - * Kent Sutherland - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * Copyright (c) 2011 Andreas Linde & Kent Sutherland. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import - -#import "BITHockeyBaseManager.h" - -@class BITCrashDetails; -@class BITCrashMetaData; - - -/** - * Custom block that handles the alert that prompts the user whether they want to send crash reports - */ -typedef void(^BITCustomAlertViewHandler)(void); - - -/** - * Crash Manager status - */ -typedef NS_ENUM(NSUInteger, BITCrashManagerStatus) { - /** - * Crash reporting is disabled - */ - BITCrashManagerStatusDisabled = 0, - /** - * User is asked each time before sending - */ - BITCrashManagerStatusAlwaysAsk = 1, - /** - * Each crash report is send automatically - */ - BITCrashManagerStatusAutoSend = 2 -}; - - -/** - * Prototype of a callback function used to execute additional user code. Called upon completion of crash - * handling, after the crash report has been written to disk. - * - * @param context The API client's supplied context value. - * - * @see `BITCrashManagerCallbacks` - * @see `[BITCrashManager setCrashCallbacks:]` - */ -typedef void (*BITCrashManagerPostCrashSignalCallback)(void *context); - -/** - * This structure contains callbacks supported by `BITCrashManager` to allow the host application to perform - * additional tasks prior to program termination after a crash has occurred. - * - * @see `BITCrashManagerPostCrashSignalCallback` - * @see `[BITCrashManager setCrashCallbacks:]` - */ -typedef struct BITCrashManagerCallbacks { - /** An arbitrary user-supplied context value. This value may be NULL. */ - void *context; - - /** - * The callback used to report caught signal information. - */ - BITCrashManagerPostCrashSignalCallback handleSignal; -} BITCrashManagerCallbacks; - - -/** - * Crash Manager alert user input - */ -typedef NS_ENUM(NSUInteger, BITCrashManagerUserInput) { - /** - * User chose not to send the crash report - */ - BITCrashManagerUserInputDontSend = 0, - /** - * User wants the crash report to be sent - */ - BITCrashManagerUserInputSend = 1, - /** - * User chose to always send crash reports - */ - BITCrashManagerUserInputAlwaysSend = 2 - -}; - - -@protocol BITCrashManagerDelegate; - -/** - The crash reporting module. - - This is the HockeySDK module for handling crash reports, including when distributed via the App Store. - As a foundation it is using the open source, reliable and async-safe crash reporting framework - [PLCrashReporter](https://code.google.com/p/plcrashreporter/). - - This module works as a wrapper around the underlying crash reporting framework and provides functionality to - detect new crashes, queues them if networking is not available, present a user interface to approve sending - the reports to the HockeyApp servers and more. - - It also provides options to add additional meta information to each crash report, like `userName`, `userEmail` - via `BITHockeyManagerDelegate` protocol, and additional textual log information via `BITCrashManagerDelegate` - protocol and a way to detect startup crashes so you can adjust your startup process to get these crash reports - too and delay your app initialization. - - Crashes are send the next time the app starts. If `crashManagerStatus` is set to `BITCrashManagerStatusAutoSend`, - crashes will be send without any user interaction, otherwise an alert will appear allowing the users to decide - whether they want to send the report or not. This module is not sending the reports right when the crash happens - deliberately, because if is not safe to implement such a mechanism while being async-safe (any Objective-C code - is _NOT_ async-safe!) and not causing more danger like a deadlock of the device, than helping. We found that users - do start the app again because most don't know what happened, and you will get by far most of the reports. - - Sending the reports on startup is done asynchronously (non-blocking). This is the only safe way to ensure - that the app won't be possibly killed by the iOS watchdog process, because startup could take too long - and the app could not react to any user input when network conditions are bad or connectivity might be - very slow. - - It is possible to check upon startup if the app crashed before using `didCrashInLastSession` and also how much - time passed between the app launch and the crash using `timeIntervalCrashInLastSessionOccurred`. This allows you - to add additional code to your app delaying the app start until the crash has been successfully send if the crash - occurred within a critical startup timeframe, e.g. after 10 seconds. The `BITCrashManagerDelegate` protocol provides - various delegates to inform the app about it's current status so you can continue the remaining app startup setup - after sending has been completed. The documentation contains a guide - [How to handle Crashes on startup](HowTo-Handle-Crashes-On-Startup) with an example on how to do that. - - More background information on this topic can be found in the following blog post by Landon Fuller, the - developer of [PLCrashReporter](https://www.plcrashreporter.org), about writing reliable and - safe crash reporting: [Reliable Crash Reporting](http://goo.gl/WvTBR) - - @warning If you start the app with the Xcode debugger attached, detecting crashes will _NOT_ be enabled! - */ - -@interface BITCrashManager : BITHockeyBaseManager - - -///----------------------------------------------------------------------------- -/// @name Configuration -///----------------------------------------------------------------------------- - -/** Set the default status of the Crash Manager - - Defines if the crash reporting feature should be disabled, ask the user before - sending each crash report or send crash reports automatically without - asking. - - The default value is `BITCrashManagerStatusAlwaysAsk`. The user can switch to - `BITCrashManagerStatusAutoSend` by choosing "Always" in the dialog (since - `showAlwaysButton` default is _YES_). - - The current value is always stored in User Defaults with the key - `BITCrashManagerStatus`. - - If you intend to implement a user setting to let them enable or disable - crash reporting, this delegate should be used to return that value. You also - have to make sure the new value is stored in the UserDefaults with the key - `BITCrashManagerStatus`. - - @see BITCrashManagerStatus - @see showAlwaysButton - */ -@property (nonatomic, assign) BITCrashManagerStatus crashManagerStatus; - - -/** - * Trap fatal signals via a Mach exception server. - * - * By default the SDK is using the safe and proven in-process BSD Signals for catching crashes. - * This option provides an option to enable catching fatal signals via a Mach exception server - * instead. - * - * We strongly advice _NOT_ to enable Mach exception handler in release versions of your apps! - * - * Default: _NO_ - * - * @warning The Mach exception handler executes in-process, and will interfere with debuggers when - * they attempt to suspend all active threads (which will include the Mach exception handler). - * Mach-based handling should _NOT_ be used when a debugger is attached. The SDK will not - * enabled catching exceptions if the app is started with the debugger running. If you attach - * the debugger during runtime, this may cause issues the Mach exception handler is enabled! - * @see isDebuggerAttached - */ -@property (nonatomic, assign, getter=isMachExceptionHandlerEnabled) BOOL enableMachExceptionHandler; - - -/** - * Enable on device symbolication for system symbols - * - * By default, the SDK does not symbolicate on the device, since this can - * take a few seconds at each crash. Also note that symbolication on the - * device might not be able to retrieve all symbols. - * - * Enable if you want to analyze crashes on unreleased OS versions. - * - * Default: _NO_ - */ -@property (nonatomic, assign, getter=isOnDeviceSymbolicationEnabled) BOOL enableOnDeviceSymbolication; - - -/** - * EXPERIMENTAL: Enable heuristics to detect the app not terminating cleanly - * - * This allows it to get a crash report if the app got killed while being in the foreground - * because of one of the following reasons: - * - * - The main thread was blocked for too long - * - The app took too long to start up - * - The app tried to allocate too much memory. If iOS did send a memory warning before killing the app because of this reason, `didReceiveMemoryWarningInLastSession` returns `YES`. - * - Permitted background duration if main thread is running in an endless loop - * - App failed to resume in time if main thread is running in an endless loop - * - If `enableMachExceptionHandler` is not activated, crashed due to stack overflow will also be reported - * - * The following kills can _NOT_ be detected: - * - * - Terminating the app takes too long - * - Permitted background duration too long for all other cases - * - App failed to resume in time for all other cases - * - possibly more cases - * - * Crash reports triggered by this mechanisms do _NOT_ contain any stack traces since the time of the kill - * cannot be intercepted and hence no stack trace of the time of the kill event can't be gathered. - * - * The heuristic is implemented as follows: - * If the app never gets a `UIApplicationDidEnterBackgroundNotification` or `UIApplicationWillTerminateNotification` - * notification, PLCrashReporter doesn't detect a crash itself, and the app starts up again, it is assumed that - * the app got either killed by iOS while being in foreground or a crash occurred that couldn't be detected. - * - * Default: _NO_ - * - * @warning This is a heuristic and it _MAY_ report false positives! It has been tested with iOS 6.1 and iOS 7. - * Depending on Apple changing notification events, new iOS version may cause more false positives! - * - * @see lastSessionCrashDetails - * @see didReceiveMemoryWarningInLastSession - * @see `BITCrashManagerDelegate considerAppNotTerminatedCleanlyReportForCrashManager:` - * @see [Apple Technical Note TN2151](https://developer.apple.com/library/ios/technotes/tn2151/_index.html) - * @see [Apple Technical Q&A QA1693](https://developer.apple.com/library/ios/qa/qa1693/_index.html) - */ -@property (nonatomic, assign, getter = isAppNotTerminatingCleanlyDetectionEnabled) BOOL enableAppNotTerminatingCleanlyDetection; - - -/** - * Set the callbacks that will be executed prior to program termination after a crash has occurred - * - * PLCrashReporter provides support for executing an application specified function in the context - * of the crash reporter's signal handler, after the crash report has been written to disk. - * - * Writing code intended for execution inside of a signal handler is exceptionally difficult, and is _NOT_ recommended! - * - * _Program Flow and Signal Handlers_ - * - * When the signal handler is called the normal flow of the program is interrupted, and your program is an unknown state. Locks may be held, the heap may be corrupt (or in the process of being updated), and your signal handler may invoke a function that was being executed at the time of the signal. This may result in deadlocks, data corruption, and program termination. - * - * _Async-Safe Functions_ - * - * A subset of functions are defined to be async-safe by the OS, and are safely callable from within a signal handler. If you do implement a custom post-crash handler, it must be async-safe. A table of POSIX-defined async-safe functions and additional information is available from the [CERT programming guide - SIG30-C](https://www.securecoding.cert.org/confluence/display/seccode/SIG30-C.+Call+only+asynchronous-safe+functions+within+signal+handlers). - * - * Most notably, the Objective-C runtime itself is not async-safe, and Objective-C may not be used within a signal handler. - * - * Documentation taken from PLCrashReporter: https://www.plcrashreporter.org/documentation/api/v1.2-rc2/async_safety.html - * - * @see BITCrashManagerPostCrashSignalCallback - * @see BITCrashManagerCallbacks - * - * @param callbacks A pointer to an initialized PLCrashReporterCallback structure, see https://www.plcrashreporter.org/documentation/api/v1.2-rc2/struct_p_l_crash_reporter_callbacks.html - */ -- (void)setCrashCallbacks: (BITCrashManagerCallbacks *) callbacks; - - -/** - Flag that determines if an "Always" option should be shown - - If enabled the crash reporting alert will also present an "Always" option, so - the user doesn't have to approve every single crash over and over again. - - If If `crashManagerStatus` is set to `BITCrashManagerStatusAutoSend`, this property - has no effect, since no alert will be presented. - - Default: _YES_ - - @see crashManagerStatus - */ -@property (nonatomic, assign, getter=shouldShowAlwaysButton) BOOL showAlwaysButton; - - -///----------------------------------------------------------------------------- -/// @name Crash Meta Information -///----------------------------------------------------------------------------- - -/** - Indicates if the app crash in the previous session - - Use this on startup, to check if the app starts the first time after it crashed - previously. You can use this also to disable specific events, like asking - the user to rate your app. - - @warning This property only has a correct value, once `[BITHockeyManager startManager]` was - invoked! - - @see lastSessionCrashDetails - */ -@property (nonatomic, readonly) BOOL didCrashInLastSession; - -/** - Provides an interface to pass user input from a custom alert to a crash report - - @param userInput Defines the users action wether to send, always send, or not to send the crash report. - @param userProvidedMetaData The content of this optional BITCrashMetaData instance will be attached to the crash report and allows to ask the user for e.g. additional comments or info. - - @return Returns YES if the input is a valid option and successfully triggered further processing of the crash report - - @see BITCrashManagerUserInput - @see BITCrashMetaData - */ -- (BOOL)handleUserInput:(BITCrashManagerUserInput)userInput withUserProvidedMetaData:(BITCrashMetaData *)userProvidedMetaData; - -/** - Lets you set a custom block which handles showing a custom UI and asking the user - whether they want to send the crash report. - - This replaces the default alert the SDK would show! - - You can use this to present any kind of user interface which asks the user for additional information, - e.g. what they did in the app before the app crashed. - - In addition to this you should always ask your users if they agree to send crash reports, send them - always or not and return the result when calling `handleUserInput:withUserProvidedCrashDescription`. - - @param alertViewHandler A block that is responsible for loading, presenting and and dismissing your custom user interface which prompts the user if they want to send crash reports. The block is also responsible for triggering further processing of the crash reports. - - @warning This is not available when compiled for Watch OS! - - @warning Block needs to call the `[BITCrashManager handleUserInput:withUserProvidedMetaData:]` method! - - @warning This needs to be set before calling `[BITHockeyManager startManager]`! - */ -- (void)setAlertViewHandler:(BITCustomAlertViewHandler)alertViewHandler; - -/** - * Provides details about the crash that occurred in the last app session - */ -@property (nonatomic, readonly) BITCrashDetails *lastSessionCrashDetails; - - -/** - Indicates if the app did receive a low memory warning in the last session - - It may happen that low memory warning where send but couldn't be logged, since iOS - killed the app before updating the flag in the filesystem did complete. - - This property may be true in case of low memory kills, but it doesn't have to be! Apps - can also be killed without the app ever receiving a low memory warning. - - Also the app could have received a low memory warning, but the reason for being killed was - actually different. - - @warning This property only has a correct value, once `[BITHockeyManager startManager]` was - invoked! - - @see enableAppNotTerminatingCleanlyDetection - @see lastSessionCrashDetails - */ -@property (nonatomic, readonly) BOOL didReceiveMemoryWarningInLastSession; - - -/** - Provides the time between startup and crash in seconds - - Use this in together with `didCrashInLastSession` to detect if the app crashed very - early after startup. This can be used to delay app initialization until the crash - report has been sent to the server or if you want to do any other actions like - cleaning up some cache data etc. - - Note that sending a crash reports starts as early as 1.5 seconds after the application - did finish launching! - - The `BITCrashManagerDelegate` protocol provides some delegates to inform if sending - a crash report was finished successfully, ended in error or was cancelled by the user. - - *Default*: _-1_ - @see didCrashInLastSession - @see BITCrashManagerDelegate - */ -@property (nonatomic, readonly) NSTimeInterval timeIntervalCrashInLastSessionOccurred; - - -///----------------------------------------------------------------------------- -/// @name Helper -///----------------------------------------------------------------------------- - -/** - * Detect if a debugger is attached to the app process - * - * This is only invoked once on app startup and can not detect if the debugger is being - * attached during runtime! - * - * @return BOOL if the debugger is attached on app startup - */ -- (BOOL)isDebuggerAttached; - - -/** - * Lets the app crash for easy testing of the SDK - * - * The best way to use this is to trigger the crash with a button action. - * - * Make sure not to let the app crash in `applicationDidFinishLaunching` or any other - * startup method! Since otherwise the app would crash before the SDK could process it. - * - * Note that our SDK provides support for handling crashes that happen early on startup. - * Check the documentation for more information on how to use this. - * - * If the SDK detects an App Store environment, it will _NOT_ cause the app to crash! - */ -- (void)generateTestCrash; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITCrashManager.m b/submodules/HockeySDK-iOS/Classes/BITCrashManager.m deleted file mode 100644 index 6b60dcc00a..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITCrashManager.m +++ /dev/null @@ -1,1713 +0,0 @@ -/* - * Author: Andreas Linde - * Kent Sutherland - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * Copyright (c) 2011 Andreas Linde & Kent Sutherland. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "HockeySDKFeatureConfig.h" - -#if HOCKEYSDK_FEATURE_CRASH_REPORTER - -#import -#import - -#import "HockeySDKPrivate.h" -#import "BITHockeyHelper.h" -#import "BITHockeyHelper+Application.h" -#import "BITHockeyAppClient.h" - -#import "BITCrashManager.h" -#import "BITCrashManagerPrivate.h" -#import "BITCrashAttachment.h" -#import "BITHockeyBaseManagerPrivate.h" -#import "BITCrashReportTextFormatter.h" -#import "BITCrashDetailsPrivate.h" -#import "BITCrashCXXExceptionHandler.h" - -#if HOCKEYSDK_FEATURE_METRICS -#import "BITMetricsManagerPrivate.h" -#import "BITChannel.h" -#import "BITPersistencePrivate.h" -#endif - -#include - -// stores the set of crashreports that have been approved but aren't sent yet -#define kBITCrashApprovedReports @"HockeySDKCrashApprovedReports" - -// keys for meta information associated to each crash -#define kBITCrashMetaUserName @"BITCrashMetaUserName" -#define kBITCrashMetaUserEmail @"BITCrashMetaUserEmail" -#define kBITCrashMetaUserID @"BITCrashMetaUserID" -#define kBITCrashMetaApplicationLog @"BITCrashMetaApplicationLog" -#define kBITCrashMetaAttachment @"BITCrashMetaAttachment" - -// internal keys -static NSString *const KBITAttachmentDictIndex = @"index"; -static NSString *const KBITAttachmentDictAttachment = @"attachment"; - -static NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus"; - -static NSString *const kBITAppWentIntoBackgroundSafely = @"BITAppWentIntoBackgroundSafely"; -static NSString *const kBITAppDidReceiveLowMemoryNotification = @"BITAppDidReceiveLowMemoryNotification"; -static NSString *const kBITAppMarketingVersion = @"BITAppMarketingVersion"; -static NSString *const kBITAppVersion = @"BITAppVersion"; -static NSString *const kBITAppOSVersion = @"BITAppOSVersion"; -static NSString *const kBITAppOSBuild = @"BITAppOSBuild"; -static NSString *const kBITAppUUIDs = @"BITAppUUIDs"; - -static NSString *const kBITFakeCrashUUID = @"BITFakeCrashUUID"; -static NSString *const kBITFakeCrashAppMarketingVersion = @"BITFakeCrashAppMarketingVersion"; -static NSString *const kBITFakeCrashAppVersion = @"BITFakeCrashAppVersion"; -static NSString *const kBITFakeCrashAppBundleIdentifier = @"BITFakeCrashAppBundleIdentifier"; -static NSString *const kBITFakeCrashOSVersion = @"BITFakeCrashOSVersion"; -static NSString *const kBITFakeCrashDeviceModel = @"BITFakeCrashDeviceModel"; -static NSString *const kBITFakeCrashAppBinaryUUID = @"BITFakeCrashAppBinaryUUID"; -static NSString *const kBITFakeCrashReport = @"BITFakeCrashAppString"; - -// We need BIT_UNUSED macro to make sure there aren't any warnings when building -// HockeySDK Distribution scheme. Since several configurations are build in this scheme -// and different features can be turned on and off we can't just use __unused attribute. -#if HOCKEYSDK_FEATURE_METRICS -static char const *BITSaveEventsFilePath; -#define BIT_UNUSED -#else -#define BIT_UNUSED __unused -#endif - -static BITCrashManagerCallbacks bitCrashCallbacks = { - .context = NULL, - .handleSignal = NULL -}; - -#if HOCKEYSDK_FEATURE_METRICS -static void bit_save_events_callback(siginfo_t __unused *info, ucontext_t __unused *uap, void __unused *context) { - - // Do not flush metrics queue if queue is empty (metrics module disabled) to not freeze the app - if (!BITTelemetryEventBuffer) { - return; - } - - // Try to get a file descriptor with our pre-filled path - int fd = open(BITSaveEventsFilePath, O_WRONLY | O_CREAT | O_TRUNC, 0644); - if (fd < 0) { - return; - } - - size_t len = strlen(BITTelemetryEventBuffer); - if (len > 0) { - // Simply write the whole string to disk - write(fd, BITTelemetryEventBuffer, len); - } - close(fd); -} -#endif - -// Proxy implementation for PLCrashReporter to keep our interface stable while this can change -static void plcr_post_crash_callback (BIT_UNUSED siginfo_t *info, BIT_UNUSED ucontext_t *uap, void *context) { -#if HOCKEYSDK_FEATURE_METRICS - bit_save_events_callback(info, uap, context); -#endif - if (bitCrashCallbacks.handleSignal != NULL) { - bitCrashCallbacks.handleSignal(context); - } -} - -static PLCrashReporterCallbacks plCrashCallbacks = { - .version = 0, - .context = NULL, - .handleSignal = plcr_post_crash_callback -}; - -// Temporary class until PLCR catches up -// We trick PLCR with an Objective-C exception. -// -// This code provides us access to the C++ exception message, including a correct stack trace. -// -@interface BITCrashCXXExceptionWrapperException : NSException - -- (instancetype)initWithCXXExceptionInfo:(const BITCrashUncaughtCXXExceptionInfo *)info; - -@property (nonatomic, readonly) const BITCrashUncaughtCXXExceptionInfo *info; - -@end - -@implementation BITCrashCXXExceptionWrapperException - -- (instancetype)initWithCXXExceptionInfo:(const BITCrashUncaughtCXXExceptionInfo *)info { - extern char* __cxa_demangle(const char* mangled_name, char* output_buffer, size_t* length, int* status); - char *demangled_name = &__cxa_demangle ? __cxa_demangle(info->exception_type_name ?: "", NULL, NULL, NULL) : NULL; - - if ((self = [super - initWithName:(NSString *)[NSString stringWithUTF8String:demangled_name ?: info->exception_type_name ?: ""] - reason:[NSString stringWithUTF8String:info->exception_message ?: ""] - userInfo:nil])) { - _info = info; - } - return self; -} - -- (NSArray *)callStackReturnAddresses { - NSMutableArray *cxxFrames = [NSMutableArray arrayWithCapacity:self.info->exception_frames_count]; - - for (uint32_t i = 0; i < self.info->exception_frames_count; ++i) { - [cxxFrames addObject:[NSNumber numberWithUnsignedLongLong:self.info->exception_frames[i]]]; - } - return cxxFrames; -} - -@end - - -// C++ Exception Handler -__attribute__((noreturn)) static void uncaught_cxx_exception_handler(const BITCrashUncaughtCXXExceptionInfo *info) { - // This relies on a LOT of sneaky internal knowledge of how PLCR works and should not be considered a long-term solution. - NSGetUncaughtExceptionHandler()([[BITCrashCXXExceptionWrapperException alloc] initWithCXXExceptionInfo:info]); - abort(); -} - -@interface BITCrashManager () - -@property (nonatomic, strong) NSMutableDictionary *approvedCrashReports; -@property (nonatomic, strong) NSMutableArray *crashFiles; -@property (nonatomic, copy) NSString *settingsFile; -@property (nonatomic, copy) NSString *analyzerInProgressFile; -@property (nonatomic) BOOL crashIdenticalCurrentVersion; -@property (nonatomic) BOOL sendingInProgress; -@property (nonatomic) BOOL isSetup; -@property (nonatomic) BOOL didLogLowMemoryWarning; -@property (nonatomic, weak) id appDidBecomeActiveObserver; -@property (nonatomic, weak) id appWillTerminateObserver; -@property (nonatomic, weak) id appDidEnterBackgroundObserver; -@property (nonatomic, weak) id appWillEnterForegroundObserver; -@property (nonatomic, weak) id appDidReceiveLowMemoryWarningObserver; -@property (nonatomic, weak) id networkDidBecomeReachableObserver; - -// Redeclare BITCrashManager properties with readwrite attribute -@property (nonatomic, readwrite) NSTimeInterval timeIntervalCrashInLastSessionOccurred; -@property (nonatomic, readwrite) BITCrashDetails *lastSessionCrashDetails; -@property (nonatomic, readwrite) BOOL didCrashInLastSession; -@property (nonatomic, readwrite) BOOL didReceiveMemoryWarningInLastSession; - -@end - -@implementation BITCrashManager - -- (instancetype)initWithAppIdentifier:(NSString *)appIdentifier appEnvironment:(BITEnvironment)environment hockeyAppClient:(BITHockeyAppClient *)hockeyAppClient { - if ((self = [super initWithAppIdentifier:appIdentifier appEnvironment:environment])) { - _delegate = nil; - _isSetup = NO; - - _hockeyAppClient = hockeyAppClient; - - _showAlwaysButton = YES; - _alertViewHandler = nil; - - _plCrashReporter = nil; - _exceptionHandler = nil; - - _crashIdenticalCurrentVersion = YES; - - _didCrashInLastSession = NO; - _timeIntervalCrashInLastSessionOccurred = -1; - _didLogLowMemoryWarning = NO; - - _approvedCrashReports = [[NSMutableDictionary alloc] init]; - - _fileManager = [[NSFileManager alloc] init]; - _crashFiles = [[NSMutableArray alloc] init]; - - _crashManagerStatus = BITCrashManagerStatusAlwaysAsk; - - if ([[NSUserDefaults standardUserDefaults] stringForKey:kBITCrashManagerStatus]) { - _crashManagerStatus = (BITCrashManagerStatus)[[NSUserDefaults standardUserDefaults] integerForKey:kBITCrashManagerStatus]; - } else { - // migrate previous setting if available - if ([[NSUserDefaults standardUserDefaults] boolForKey:@"BITCrashAutomaticallySendReports"]) { - _crashManagerStatus = BITCrashManagerStatusAutoSend; - [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"BITCrashAutomaticallySendReports"]; - } - [[NSUserDefaults standardUserDefaults] setInteger:_crashManagerStatus forKey:kBITCrashManagerStatus]; - } - - _crashesDir = bit_settingsDir(); - _settingsFile = [_crashesDir stringByAppendingPathComponent:BITHOCKEY_CRASH_SETTINGS]; - _analyzerInProgressFile = [_crashesDir stringByAppendingPathComponent:BITHOCKEY_CRASH_ANALYZER]; - - - if (!BITHockeyBundle() && !bit_isRunningInAppExtension()) { - BITHockeyLogWarning(@"[HockeySDK] WARNING: %@ is missing, will send reports automatically!", BITHOCKEYSDK_BUNDLE); - } - } - return self; -} - - -- (void) dealloc { - [self unregisterObservers]; -} - - -- (void)setCrashManagerStatus:(BITCrashManagerStatus)crashManagerStatus { - _crashManagerStatus = crashManagerStatus; - - [[NSUserDefaults standardUserDefaults] setInteger:crashManagerStatus forKey:kBITCrashManagerStatus]; -} - -- (void)setServerURL:(NSString *)serverURL { - if ([serverURL isEqualToString:super.serverURL]) { return; } - - super.serverURL = serverURL; - self.hockeyAppClient = [[BITHockeyAppClient alloc] initWithBaseURL:[NSURL URLWithString:serverURL]]; -} - -#pragma mark - Private - -/** - * Save all settings - * - * This saves the list of approved crash reports - */ -- (void)saveSettings { - NSError *error = nil; - - NSMutableDictionary *rootObj = [NSMutableDictionary dictionaryWithCapacity:2]; - if (self.approvedCrashReports && [self.approvedCrashReports count] > 0) { - [rootObj setObject:self.approvedCrashReports forKey:kBITCrashApprovedReports]; - } - - NSData *plist = [NSPropertyListSerialization dataWithPropertyList:(id)rootObj format:NSPropertyListBinaryFormat_v1_0 options:0 error:&error]; - - if (plist) { - [plist writeToFile:self.settingsFile atomically:YES]; - } else { - BITHockeyLogError(@"ERROR: Writing settings. %@", [error description]); - } -} - -/** - * Load all settings - * - * This contains the list of approved crash reports - */ -- (void)loadSettings { - NSError *error = nil; - NSPropertyListFormat format; - - if (![self.fileManager fileExistsAtPath:self.settingsFile]) - return; - - NSData *plist = [NSData dataWithContentsOfFile:self.settingsFile]; - if (plist) { - NSDictionary *rootObj = (NSDictionary *)[NSPropertyListSerialization - propertyListWithData:plist - options:NSPropertyListMutableContainersAndLeaves - format:&format - error:&error]; - - if ([rootObj objectForKey:kBITCrashApprovedReports]) - [self.approvedCrashReports setDictionary:(NSDictionary *)[rootObj objectForKey:kBITCrashApprovedReports]]; - } else { - BITHockeyLogError(@"ERROR: Reading crash manager settings."); - } -} - - -/** - * Remove a cached crash report - * - * @param filename The base filename of the crash report - */ -- (void)cleanCrashReportWithFilename:(NSString *)filename { - if (!filename) return; - - NSError *error = NULL; - - [self.fileManager removeItemAtPath:filename error:&error]; - [self.fileManager removeItemAtPath:[filename stringByAppendingString:@".data"] error:&error]; - [self.fileManager removeItemAtPath:[filename stringByAppendingString:@".meta"] error:&error]; - [self.fileManager removeItemAtPath:[filename stringByAppendingString:@".desc"] error:&error]; - - NSString *cacheFilename = [filename lastPathComponent]; - [self removeKeyFromKeychain:[NSString stringWithFormat:@"%@.%@", cacheFilename, kBITCrashMetaUserName]]; - [self removeKeyFromKeychain:[NSString stringWithFormat:@"%@.%@", cacheFilename, kBITCrashMetaUserEmail]]; - [self removeKeyFromKeychain:[NSString stringWithFormat:@"%@.%@", cacheFilename, kBITCrashMetaUserID]]; - - [self.crashFiles removeObject:filename]; - [self.approvedCrashReports removeObjectForKey:filename]; - - [self saveSettings]; -} - -/** - * Remove all crash reports and stored meta data for each from the file system and keychain - * - * This is currently only used as a helper method for tests - */ -- (void)cleanCrashReports { - for (NSUInteger i=0; i < [self.crashFiles count]; i++) { - [self cleanCrashReportWithFilename:[self.crashFiles objectAtIndex:i]]; - } -} - -- (BOOL)persistAttachment:(BITHockeyAttachment *)attachment withFilename:(NSString *)filename { - NSString *attachmentFilename = [filename stringByAppendingString:@".data"]; - NSMutableData *data = [[NSMutableData alloc] init]; - NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; - - [archiver encodeObject:attachment forKey:kBITCrashMetaAttachment]; - - [archiver finishEncoding]; - - return [data writeToFile:attachmentFilename atomically:YES]; -} - -- (void)persistUserProvidedMetaData:(BITCrashMetaData *)userProvidedMetaData { - if (!userProvidedMetaData) return; - - if (userProvidedMetaData.userProvidedDescription && [userProvidedMetaData.userProvidedDescription length] > 0) { - NSError *error; - [userProvidedMetaData.userProvidedDescription writeToFile:[NSString stringWithFormat:@"%@.desc", [self.crashesDir stringByAppendingPathComponent: self.lastCrashFilename]] atomically:YES encoding:NSUTF8StringEncoding error:&error]; - } - - if (userProvidedMetaData.userName && [userProvidedMetaData.userName length] > 0) { - [self addStringValueToKeychain:userProvidedMetaData.userName forKey:[NSString stringWithFormat:@"%@.%@", self.lastCrashFilename, kBITCrashMetaUserName]]; - - } - - if (userProvidedMetaData.userEmail && [userProvidedMetaData.userEmail length] > 0) { - [self addStringValueToKeychain:userProvidedMetaData.userEmail forKey:[NSString stringWithFormat:@"%@.%@", self.lastCrashFilename, kBITCrashMetaUserEmail]]; - } - - if (userProvidedMetaData.userID && [userProvidedMetaData.userID length] > 0) { - [self addStringValueToKeychain:userProvidedMetaData.userID forKey:[NSString stringWithFormat:@"%@.%@", self.lastCrashFilename, kBITCrashMetaUserID]]; - - } -} - -/** - * Read the attachment data from the stored file - * - * @param filename The crash report file path - * - * @return an BITHockeyAttachment instance or nil - */ -- (BITHockeyAttachment *)attachmentForCrashReport:(NSString *)filename { - NSString *attachmentFilename = [filename stringByAppendingString:@".data"]; - - if (![self.fileManager fileExistsAtPath:attachmentFilename]) - return nil; - - - NSData *codedData = [[NSData alloc] initWithContentsOfFile:attachmentFilename]; - if (!codedData) - return nil; - - NSKeyedUnarchiver *unarchiver = nil; - - @try { - unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:codedData]; - } - @catch (NSException __unused *exception) { - return nil; - } - - if ([unarchiver containsValueForKey:kBITCrashMetaAttachment]) { - BITHockeyAttachment *attachment = [unarchiver decodeObjectForKey:kBITCrashMetaAttachment]; - return attachment; - } - - return nil; -} - -/** - * Extract all app specific UUIDs from the crash reports - * - * This allows us to send the UUIDs in the XML construct to the server, so the server does not need to parse the crash report for this data. - * The app specific UUIDs help to identify which dSYMs are needed to symbolicate this crash report. - * - * @param report The crash report from PLCrashReporter - * - * @return XML structure with the app specific UUIDs - */ -- (NSString *) extractAppUUIDs:(BITPLCrashReport *)report { - NSMutableString *uuidString = [NSMutableString string]; - NSArray *uuidArray = [BITCrashReportTextFormatter arrayOfAppUUIDsForCrashReport:report]; - - for (NSDictionary *element in uuidArray) { - if ([element objectForKey:kBITBinaryImageKeyType] && [element objectForKey:kBITBinaryImageKeyArch] && [element objectForKey:kBITBinaryImageKeyUUID]) { - [uuidString appendFormat:@"%@", - [element objectForKey:kBITBinaryImageKeyType], - [element objectForKey:kBITBinaryImageKeyArch], - [element objectForKey:kBITBinaryImageKeyUUID] - ]; - } - } - - return uuidString; -} - -- (void) registerObservers { - __weak typeof(self) weakSelf = self; - - if(nil == self.appDidBecomeActiveObserver) { - NSNotificationName name = UIApplicationDidBecomeActiveNotification; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wpartial-availability" - if (bit_isRunningInAppExtension() && &NSExtensionHostDidBecomeActiveNotification != NULL) { - name = NSExtensionHostDidBecomeActiveNotification; - } -#pragma clang diagnostic pop - self.appDidBecomeActiveObserver = [[NSNotificationCenter defaultCenter] addObserverForName:name - object:nil - queue:NSOperationQueue.mainQueue - usingBlock:^(NSNotification __unused *note) { - typeof(self) strongSelf = weakSelf; - [strongSelf triggerDelayedProcessing]; - }]; - } - - if(nil == self.networkDidBecomeReachableObserver) { - self.networkDidBecomeReachableObserver = [[NSNotificationCenter defaultCenter] addObserverForName:BITHockeyNetworkDidBecomeReachableNotification - object:nil - queue:NSOperationQueue.mainQueue - usingBlock:^(NSNotification __unused *note) { - typeof(self) strongSelf = weakSelf; - [strongSelf triggerDelayedProcessing]; - }]; - } - - if (nil == self.appWillTerminateObserver) { - self.appWillTerminateObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification - object:nil - queue:NSOperationQueue.mainQueue - usingBlock:^(NSNotification __unused *note) { - typeof(self) strongSelf = weakSelf; - [strongSelf leavingAppSafely]; - }]; - } - - if (nil == self.appDidEnterBackgroundObserver) { - NSNotificationName name = UIApplicationDidEnterBackgroundNotification; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wpartial-availability" - if (bit_isRunningInAppExtension() && &NSExtensionHostDidEnterBackgroundNotification != NULL) { - name = NSExtensionHostDidEnterBackgroundNotification; - } -#pragma clang diagnostic pop - self.appDidEnterBackgroundObserver = [[NSNotificationCenter defaultCenter] addObserverForName:name - object:nil - queue:NSOperationQueue.mainQueue - usingBlock:^(NSNotification __unused *note) { - typeof(self) strongSelf = weakSelf; - [strongSelf leavingAppSafely]; - }]; - } - - if (nil == self.appWillEnterForegroundObserver) { - NSNotificationName name = UIApplicationWillEnterForegroundNotification; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wpartial-availability" - if (bit_isRunningInAppExtension() && &NSExtensionHostWillEnterForegroundNotification != NULL) { - name = NSExtensionHostWillEnterForegroundNotification; - } -#pragma clang diagnostic pop - self.appWillEnterForegroundObserver = [[NSNotificationCenter defaultCenter] addObserverForName:name - object:nil - queue:NSOperationQueue.mainQueue - usingBlock:^(NSNotification __unused *note) { - typeof(self) strongSelf = weakSelf; - [strongSelf appEnteredForeground]; - }]; - } - - if (nil == self.appDidReceiveLowMemoryWarningObserver) { - if (bit_isRunningInAppExtension()) { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_MEMORYPRESSURE, 0, DISPATCH_MEMORYPRESSURE_WARN|DISPATCH_MEMORYPRESSURE_CRITICAL, dispatch_get_main_queue()); - dispatch_source_set_event_handler(source, ^{ - if (!self.didLogLowMemoryWarning) { - [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kBITAppDidReceiveLowMemoryNotification]; - self.didLogLowMemoryWarning = YES; - } - }); - dispatch_resume(source); - }); - } else { - self.appDidReceiveLowMemoryWarningObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidReceiveMemoryWarningNotification - object:nil - queue:NSOperationQueue.mainQueue - usingBlock:^(NSNotification __unused *note) { - // we only need to log this once - if (!self.didLogLowMemoryWarning) { - [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kBITAppDidReceiveLowMemoryNotification]; - self.didLogLowMemoryWarning = YES; - - } - }]; - } - } -} - -- (void) unregisterObservers { - [self unregisterObserver:self.appDidBecomeActiveObserver]; - [self unregisterObserver:self.appWillTerminateObserver]; - [self unregisterObserver:self.appDidEnterBackgroundObserver]; - [self unregisterObserver:self.appWillEnterForegroundObserver]; - [self unregisterObserver:self.appDidReceiveLowMemoryWarningObserver]; - - [self unregisterObserver:self.networkDidBecomeReachableObserver]; -} - -- (void) unregisterObserver:(id)observer { - if (observer) { - [[NSNotificationCenter defaultCenter] removeObserver:observer]; - observer = nil; - } -} - -- (void)leavingAppSafely { - if (self.isAppNotTerminatingCleanlyDetectionEnabled) { - [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kBITAppWentIntoBackgroundSafely]; - } -} - -- (void)appEnteredForeground { - // we disable kill detection while the debugger is running, since we'd get only false positives if the app is terminated by the user using the debugger - if (self.isDebuggerAttached) { - [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kBITAppWentIntoBackgroundSafely]; - } else if (self.isAppNotTerminatingCleanlyDetectionEnabled) { - [[NSUserDefaults standardUserDefaults] setBool:NO forKey:kBITAppWentIntoBackgroundSafely]; - - static dispatch_once_t predAppData; - - dispatch_once(&predAppData, ^{ - id marketingVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; - if (marketingVersion && [marketingVersion isKindOfClass:[NSString class]]) - [[NSUserDefaults standardUserDefaults] setObject:marketingVersion forKey:kBITAppMarketingVersion]; - - id bundleVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]; - if (bundleVersion && [bundleVersion isKindOfClass:[NSString class]]) - [[NSUserDefaults standardUserDefaults] setObject:bundleVersion forKey:kBITAppVersion]; - - [[NSUserDefaults standardUserDefaults] setObject:[[UIDevice currentDevice] systemVersion] forKey:kBITAppOSVersion]; - [[NSUserDefaults standardUserDefaults] setObject:[self osBuild] forKey:kBITAppOSBuild]; - - NSString *uuidString =[NSString stringWithFormat:@"%@", - [self deviceArchitecture], - [self executableUUID] - ]; - - [[NSUserDefaults standardUserDefaults] setObject:uuidString forKey:kBITAppUUIDs]; - }); - } -} - -- (NSString *)deviceArchitecture { - NSString *archName = @"???"; - - size_t size; - cpu_type_t type; - cpu_subtype_t subtype; - size = sizeof(type); - if (sysctlbyname("hw.cputype", &type, &size, NULL, 0)) - return archName; - - size = sizeof(subtype); - if (sysctlbyname("hw.cpusubtype", &subtype, &size, NULL, 0)) - return archName; - - archName = [BITCrashReportTextFormatter bit_archNameFromCPUType:type subType:subtype] ?: @"???"; - - return archName; -} - -- (NSString *)osBuild { - size_t size; - sysctlbyname("kern.osversion", NULL, &size, NULL, 0); - char *answer = (char*)malloc(size); - if (answer == NULL) - return nil; - sysctlbyname("kern.osversion", answer, &size, NULL, 0); - NSString *osBuild = [NSString stringWithCString:answer encoding: NSUTF8StringEncoding]; - free(answer); - return osBuild; -} - -/** - * Get the userID from the delegate which should be stored with the crash report - * - * @return The userID value - */ -- (NSString *)userIDForCrashReport { - NSString *userID; -#if HOCKEYSDK_FEATURE_AUTHENTICATOR - // if we have an identification from BITAuthenticator, use this as a default. - if (( - self.installationIdentificationType == BITAuthenticatorIdentificationTypeAnonymous || - self.installationIdentificationType == BITAuthenticatorIdentificationTypeDevice - ) && - self.installationIdentification) { - userID = self.installationIdentification; - } -#endif - - // first check the global keychain storage - NSString *userIdFromKeychain = [self stringValueFromKeychainForKey:kBITHockeyMetaUserID]; - if (userIdFromKeychain) { - userID = userIdFromKeychain; - } - id strongDelegate = [BITHockeyManager sharedHockeyManager].delegate; - if ([strongDelegate respondsToSelector:@selector(userIDForHockeyManager:componentManager:)]) { - userID = [strongDelegate userIDForHockeyManager:[BITHockeyManager sharedHockeyManager] componentManager:self]; - } - return userID ?: @""; -} - -/** - * Get the userName from the delegate which should be stored with the crash report - * - * @return The userName value - */ -- (NSString *)userNameForCrashReport { - // first check the global keychain storage - NSString *username = [self stringValueFromKeychainForKey:kBITHockeyMetaUserName] ?: @""; - id strongDelegate = [BITHockeyManager sharedHockeyManager].delegate; - if ([strongDelegate respondsToSelector:@selector(userNameForHockeyManager:componentManager:)]) { - username = [strongDelegate userNameForHockeyManager:[BITHockeyManager sharedHockeyManager] componentManager:self] ?: @""; - } - return username; -} - -/** - * Get the userEmail from the delegate which should be stored with the crash report - * - * @return The userEmail value - */ -- (NSString *)userEmailForCrashReport { - // first check the global keychain storage - NSString *useremail = [self stringValueFromKeychainForKey:kBITHockeyMetaUserEmail] ?: @""; - -#if HOCKEYSDK_FEATURE_AUTHENTICATOR - // if we have an identification from BITAuthenticator, use this as a default. - if (( - self.installationIdentificationType == BITAuthenticatorIdentificationTypeHockeyAppEmail || - self.installationIdentificationType == BITAuthenticatorIdentificationTypeHockeyAppUser || - self.installationIdentificationType == BITAuthenticatorIdentificationTypeWebAuth - ) && - self.installationIdentification) { - useremail = self.installationIdentification; - } -#endif - id strongDelegate = [BITHockeyManager sharedHockeyManager].delegate; - if ([strongDelegate respondsToSelector:@selector(userEmailForHockeyManager:componentManager:)]) { - useremail = [strongDelegate userEmailForHockeyManager:[BITHockeyManager sharedHockeyManager] componentManager:self] ?: @""; - } - return useremail; -} - -#pragma mark - CrashCallbacks - -/** - * Set the callback for PLCrashReporter - * - * @param callbacks BITCrashManagerCallbacks instance - */ -- (void)setCrashCallbacks:(BITCrashManagerCallbacks *)callbacks { - if (!callbacks) return; - if (self.isSetup) { - BITHockeyLogWarning(@"WARNING: CrashCallbacks need to be configured before calling startManager!"); - } - - // set our proxy callback struct - bitCrashCallbacks.context = callbacks->context; - bitCrashCallbacks.handleSignal = callbacks->handleSignal; - - // set the PLCrashReporterCallbacks struct - plCrashCallbacks.context = callbacks->context; -} - -#if HOCKEYSDK_FEATURE_METRICS -- (void)configDefaultCrashCallback { - BITMetricsManager *metricsManager = [BITHockeyManager sharedHockeyManager].metricsManager; - BITPersistence *persistence = metricsManager.persistence; - BITSaveEventsFilePath = strdup([persistence fileURLForType:BITPersistenceTypeTelemetry].UTF8String); -} -#endif - -#pragma mark - Public - -- (void)setAlertViewHandler:(BITCustomAlertViewHandler)alertViewHandler{ - _alertViewHandler = alertViewHandler; -} - - -- (BOOL)isDebuggerAttached { - return bit_isDebuggerAttached(); -} - - -- (void)generateTestCrash { - if (self.appEnvironment != BITEnvironmentAppStore) { - - if ([self isDebuggerAttached]) { - BITHockeyLogWarning(@"[HockeySDK] WARNING: The debugger is attached. The following crash cannot be detected by the SDK!"); - } - - __builtin_trap(); - } -} - -/** - * Write a meta file for a new crash report - * - * @param filename the crash reports temp filename - */ -- (void)storeMetaDataForCrashReportFilename:(NSString *)filename { - BITHockeyLogVerbose(@"VERBOSE: Storing meta data for crash report with filename %@", filename); - NSError *error = NULL; - NSMutableDictionary *metaDict = [NSMutableDictionary dictionaryWithCapacity:4]; - NSString *applicationLog = @""; - - [self addStringValueToKeychain:[self userNameForCrashReport] forKey:[NSString stringWithFormat:@"%@.%@", filename, kBITCrashMetaUserName]]; - [self addStringValueToKeychain:[self userEmailForCrashReport] forKey:[NSString stringWithFormat:@"%@.%@", filename, kBITCrashMetaUserEmail]]; - [self addStringValueToKeychain:[self userIDForCrashReport] forKey:[NSString stringWithFormat:@"%@.%@", filename, kBITCrashMetaUserID]]; - id strongDelegate = self.delegate; - if ([strongDelegate respondsToSelector:@selector(applicationLogForCrashManager:)]) { - applicationLog = [strongDelegate applicationLogForCrashManager:self] ?: @""; - } - [metaDict setObject:applicationLog forKey:kBITCrashMetaApplicationLog]; - - if ([strongDelegate respondsToSelector:@selector(attachmentForCrashManager:)]) { - BITHockeyLogVerbose(@"VERBOSE: Processing attachment for crash report with filename %@", filename); - BITHockeyAttachment *attachment = [strongDelegate attachmentForCrashManager:self]; - - if (attachment && attachment.hockeyAttachmentData) { - BOOL success = [self persistAttachment:attachment withFilename:[self.crashesDir stringByAppendingPathComponent: filename]]; - if (!success) { - BITHockeyLogError(@"ERROR: Persisting the crash attachment failed"); - } else { - BITHockeyLogVerbose(@"VERBOSE: Crash attachment successfully persisted."); - } - } else { - BITHockeyLogDebug(@"INFO: Crash attachment was nil"); - } - } - - NSData *plist = [NSPropertyListSerialization dataWithPropertyList:(id)metaDict - format:NSPropertyListBinaryFormat_v1_0 - options:0 - error:&error]; - if (plist) { - BOOL success = [plist writeToFile:[self.crashesDir stringByAppendingPathComponent:(NSString *)[filename stringByAppendingPathExtension:@"meta"]] atomically:YES]; - if (!success) { - BITHockeyLogError(@"ERROR: Writing crash meta data failed."); - } - } else { - BITHockeyLogError(@"ERROR: Writing crash meta data failed. %@", error); - } - BITHockeyLogVerbose(@"VERBOSE: Storing crash meta data finished."); -} - -- (BOOL)handleUserInput:(BITCrashManagerUserInput)userInput withUserProvidedMetaData:(BITCrashMetaData *)userProvidedMetaData { - id strongDelegate = self.delegate; - switch (userInput) { - case BITCrashManagerUserInputDontSend: - if ([strongDelegate respondsToSelector:@selector(crashManagerWillCancelSendingCrashReport:)]) { - [strongDelegate crashManagerWillCancelSendingCrashReport:self]; - } - - if (self.lastCrashFilename) - [self cleanCrashReportWithFilename:[self.crashesDir stringByAppendingPathComponent: self.lastCrashFilename]]; - - return YES; - - case BITCrashManagerUserInputSend: - if (userProvidedMetaData) - [self persistUserProvidedMetaData:userProvidedMetaData]; - - [self approveLatestCrashReport]; - [self sendNextCrashReport]; - return YES; - - case BITCrashManagerUserInputAlwaysSend: - self.crashManagerStatus = BITCrashManagerStatusAutoSend; - [[NSUserDefaults standardUserDefaults] setInteger:self.crashManagerStatus forKey:kBITCrashManagerStatus]; - - if ([strongDelegate respondsToSelector:@selector(crashManagerWillSendCrashReportsAlways:)]) { - [strongDelegate crashManagerWillSendCrashReportsAlways:self]; - } - - if (userProvidedMetaData) - [self persistUserProvidedMetaData:userProvidedMetaData]; - - [self approveLatestCrashReport]; - [self sendNextCrashReport]; - return YES; - - default: - return NO; - } - -} - -#pragma mark - PLCrashReporter - -/** - * Process new crash reports provided by PLCrashReporter - * - * Parse the new crash report and gather additional meta data from the app which will be stored along the crash report - */ -- (void) handleCrashReport { - BITHockeyLogVerbose(@"VERBOSE: Handling crash report"); - NSError *error = NULL; - - if (!self.plCrashReporter) return; - - // check if the next call ran successfully the last time - if (![self.fileManager fileExistsAtPath:self.analyzerInProgressFile]) { - // mark the start of the routine - [self.fileManager createFileAtPath:self.analyzerInProgressFile contents:nil attributes:nil]; - BITHockeyLogVerbose(@"VERBOSE: AnalyzerInProgress file created"); - - [self saveSettings]; - - // Try loading the crash report - NSData *crashData = [[NSData alloc] initWithData:[self.plCrashReporter loadPendingCrashReportDataAndReturnError: &error]]; - - NSString *cacheFilename = [NSString stringWithFormat: @"%.0f", [NSDate timeIntervalSinceReferenceDate]]; - self.lastCrashFilename = cacheFilename; - - if (crashData == nil) { - BITHockeyLogError(@"ERROR: Could not load crash report: %@", error); - } else { - // get the startup timestamp from the crash report, and the file timestamp to calculate the timeinterval when the crash happened after startup - BITPLCrashReport *report = [[BITPLCrashReport alloc] initWithData:crashData error:&error]; - - if (report == nil) { - BITHockeyLogWarning(@"WARNING: Could not parse crash report"); - } else { - NSDate *appStartTime = nil; - NSDate *appCrashTime = nil; - if ([report.processInfo respondsToSelector:@selector(processStartTime)]) { - if (report.systemInfo.timestamp && report.processInfo.processStartTime) { - appStartTime = report.processInfo.processStartTime; - appCrashTime =report.systemInfo.timestamp; - self.timeIntervalCrashInLastSessionOccurred = [report.systemInfo.timestamp timeIntervalSinceDate:report.processInfo.processStartTime]; - } - } - - [crashData writeToFile:[self.crashesDir stringByAppendingPathComponent: cacheFilename] atomically:YES]; - - NSString *incidentIdentifier = @"???"; - if (report.uuidRef != NULL) { - incidentIdentifier = (NSString *) CFBridgingRelease(CFUUIDCreateString(NULL, report.uuidRef)); - } - - NSString *reporterKey = bit_appAnonID(NO) ?: @""; - - self.lastSessionCrashDetails = [[BITCrashDetails alloc] initWithIncidentIdentifier:incidentIdentifier - reporterKey:reporterKey - signal:report.signalInfo.name - exceptionName:report.exceptionInfo.exceptionName - exceptionReason:report.exceptionInfo.exceptionReason - appStartTime:appStartTime - crashTime:appCrashTime - osVersion:report.systemInfo.operatingSystemVersion - osBuild:report.systemInfo.operatingSystemBuild - appVersion:report.applicationInfo.applicationMarketingVersion - appBuild:report.applicationInfo.applicationVersion - appProcessIdentifier:report.processInfo.processID - ]; - - // fetch and store the meta data after setting _lastSessionCrashDetails, so the property can be used in the protocol methods - [self storeMetaDataForCrashReportFilename:cacheFilename]; - } - } - } else { - BITHockeyLogWarning(@"WARNING: AnalyzerInProgress file found, handling crash report skipped"); - } - - // Purge the report - // mark the end of the routine - if ([self.fileManager fileExistsAtPath:self.analyzerInProgressFile]) { - [self.fileManager removeItemAtPath:self.analyzerInProgressFile error:&error]; - } - - [self saveSettings]; - - [self.plCrashReporter purgePendingCrashReport]; -} - -/** - Get the filename of the first not approved crash report - - @return NSString Filename of the first found not approved crash report - */ -- (NSString *)firstNotApprovedCrashReport { - if ((!self.approvedCrashReports || [self.approvedCrashReports count] == 0) && [self.crashFiles count] > 0) { - return [self.crashFiles objectAtIndex:0]; - } - - for (NSUInteger i=0; i < [self.crashFiles count]; i++) { - NSString *filename = [self.crashFiles objectAtIndex:i]; - - if (![self.approvedCrashReports objectForKey:filename]) return filename; - } - - return nil; -} - -/** - * Check if there are any new crash reports that are not yet processed - * - * @return `YES` if there is at least one new crash report found, `NO` otherwise - */ -- (BOOL)hasPendingCrashReport { - if (self.crashManagerStatus == BITCrashManagerStatusDisabled) return NO; - - if ([self.fileManager fileExistsAtPath:self.crashesDir]) { - NSError *error = NULL; - - NSArray *dirArray = [self.fileManager contentsOfDirectoryAtPath:self.crashesDir error:&error]; - - for (NSString *file in dirArray) { - NSString *filePath = [self.crashesDir stringByAppendingPathComponent:file]; - - NSDictionary *fileAttributes = [self.fileManager attributesOfItemAtPath:filePath error:&error]; - if ([[fileAttributes objectForKey:NSFileType] isEqualToString:NSFileTypeRegular] && - [[fileAttributes objectForKey:NSFileSize] intValue] > 0 && - ![file hasSuffix:@".DS_Store"] && - ![file hasSuffix:@".analyzer"] && - ![file hasSuffix:@".plist"] && - ![file hasSuffix:@".data"] && - ![file hasSuffix:@".meta"] && - ![file hasSuffix:@".desc"]) { - [self.crashFiles addObject:filePath]; - } - } - } - - if ([self.crashFiles count] > 0) { - BITHockeyLogDebug(@"INFO: %lu pending crash reports found.", (unsigned long)[self.crashFiles count]); - return YES; - } else { - if (self.didCrashInLastSession) { - id strongDelegate = self.delegate; - if ([strongDelegate respondsToSelector:@selector(crashManagerWillCancelSendingCrashReport:)]) { - [strongDelegate crashManagerWillCancelSendingCrashReport:self]; - } - - self.didCrashInLastSession = NO; - } - - return NO; - } -} - - -#pragma mark - Crash Report Processing - -// store the latest crash report as user approved, so if it fails it will retry automatically -- (void)approveLatestCrashReport { - [self.approvedCrashReports setObject:[NSNumber numberWithBool:YES] forKey:[self.crashesDir stringByAppendingPathComponent: self.lastCrashFilename]]; - [self saveSettings]; -} - -- (void)triggerDelayedProcessing { - BITHockeyLogVerbose(@"VERBOSE: Triggering delayed crash processing."); - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(invokeDelayedProcessing) object:nil]; - [self performSelector:@selector(invokeDelayedProcessing) withObject:nil afterDelay:0.5]; -} - -/** - * Delayed startup processing for everything that does not to be done in the app startup runloop - * - * - Checks if there is another exception handler installed that may block ours - * - Present UI if the user has to approve new crash reports - * - Send pending approved crash reports - */ -- (void)invokeDelayedProcessing { -#if !defined (HOCKEYSDK_CONFIGURATION_ReleaseCrashOnlyExtensions) - if (!bit_isRunningInAppExtension() && [BITHockeyHelper applicationState] != BITApplicationStateActive) { - return; - } -#endif - - BITHockeyLogDebug(@"INFO: Start delayed CrashManager processing"); - - // was our own exception handler successfully added? - if (self.exceptionHandler) { - // get the current top level error handler - NSUncaughtExceptionHandler *currentHandler = NSGetUncaughtExceptionHandler(); - - // If the top level error handler differs from our own, then at least another one was added. - // This could cause exception crashes not to be reported to HockeyApp. See log message for details. - if (self.exceptionHandler != currentHandler) { - BITHockeyLogWarning(@"[HockeySDK] WARNING: Another exception handler was added. If this invokes any kind of exit() after processing the exception, which causes any subsequent error handler not to be invoked, these crashes will NOT be reported to HockeyApp!"); - } - } - - if (!self.sendingInProgress && [self hasPendingCrashReport]) { - self.sendingInProgress = YES; - - NSString *notApprovedReportFilename = [self firstNotApprovedCrashReport]; - - // this can happen in case there is a non approved crash report but it didn't happen in the previous app session - if (notApprovedReportFilename && !self.lastCrashFilename) { - self.lastCrashFilename = [notApprovedReportFilename lastPathComponent]; - } - - if (!BITHockeyBundle() || bit_isRunningInAppExtension()) { - [self approveLatestCrashReport]; - [self sendNextCrashReport]; - -#if !defined (HOCKEYSDK_CONFIGURATION_ReleaseCrashOnlyExtensions) - - } else if (self.crashManagerStatus != BITCrashManagerStatusAutoSend && notApprovedReportFilename) { - id strongDelegate = self.delegate; - if ([strongDelegate respondsToSelector:@selector(crashManagerWillShowSubmitCrashReportAlert:)]) { - [strongDelegate crashManagerWillShowSubmitCrashReportAlert:self]; - } - - NSString *appName = bit_appName(BITHockeyLocalizedString(@"HockeyAppNamePlaceholder")); - NSString *alertDescription = [NSString stringWithFormat:BITHockeyLocalizedString(@"CrashDataFoundAnonymousDescription"), appName]; - - // the crash report is not anonymous any more if username or useremail are not nil - NSString *userid = [self userIDForCrashReport]; - NSString *username = [self userNameForCrashReport]; - NSString *useremail = [self userEmailForCrashReport]; - - if ((userid && [userid length] > 0) || - (username && [username length] > 0) || - (useremail && [useremail length] > 0)) { - alertDescription = [NSString stringWithFormat:BITHockeyLocalizedString(@"CrashDataFoundDescription"), appName]; - } - - if (self.alertViewHandler) { - self.alertViewHandler(); - } else { - __weak typeof(self) weakSelf = self; - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:BITHockeyLocalizedString(@"CrashDataFoundTitle"), appName] - message:alertDescription - preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *cancelAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"CrashDontSendReport") - style:UIAlertActionStyleCancel - handler:^(UIAlertAction __unused *action) { - typeof(self) strongSelf = weakSelf; - [strongSelf handleUserInput:BITCrashManagerUserInputDontSend withUserProvidedMetaData:nil]; - }]; - [alertController addAction:cancelAction]; - UIAlertAction *sendAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"CrashSendReport") - style:UIAlertActionStyleDefault - handler:^(UIAlertAction __unused *action) { - typeof(self) strongSelf = weakSelf; - [strongSelf handleUserInput:BITCrashManagerUserInputSend withUserProvidedMetaData:nil]; - }]; - [alertController addAction:sendAction]; - if (self.shouldShowAlwaysButton) { - UIAlertAction *alwaysSendAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"CrashSendReportAlways") - style:UIAlertActionStyleDefault - handler:^(UIAlertAction __unused *action) { - typeof(self) strongSelf = weakSelf; - [strongSelf handleUserInput:BITCrashManagerUserInputAlwaysSend withUserProvidedMetaData:nil]; - }]; - - [alertController addAction:alwaysSendAction]; - } - - [self showAlertController:alertController]; - } -#endif /* !defined (HOCKEYSDK_CONFIGURATION_ReleaseCrashOnlyExtensions) */ - - } else { - [self approveLatestCrashReport]; - [self sendNextCrashReport]; - } - } -} - -/** - * Main startup sequence initializing PLCrashReporter if it wasn't disabled - */ -- (void)startManager { - if (self.crashManagerStatus == BITCrashManagerStatusDisabled) return; - - [self registerObservers]; - - [self loadSettings]; - - if (!self.isSetup) { - static dispatch_once_t plcrPredicate; - dispatch_once(&plcrPredicate, ^{ - /* Configure our reporter */ - - PLCrashReporterSignalHandlerType signalHandlerType = PLCrashReporterSignalHandlerTypeBSD; - if (self.isMachExceptionHandlerEnabled) { - signalHandlerType = PLCrashReporterSignalHandlerTypeMach; - } - - PLCrashReporterSymbolicationStrategy symbolicationStrategy = PLCrashReporterSymbolicationStrategyNone; - if (self.isOnDeviceSymbolicationEnabled) { - symbolicationStrategy = PLCrashReporterSymbolicationStrategyAll; - } - - BITPLCrashReporterConfig *config = [[BITPLCrashReporterConfig alloc] initWithSignalHandlerType: signalHandlerType - symbolicationStrategy: symbolicationStrategy]; - self.plCrashReporter = [[BITPLCrashReporter alloc] initWithConfiguration: config]; - - // Check if we previously crashed - if ([self.plCrashReporter hasPendingCrashReport]) { - self.didCrashInLastSession = YES; - [self handleCrashReport]; - } - - // The actual signal and mach handlers are only registered when invoking `enableCrashReporterAndReturnError` - // So it is safe enough to only disable the following part when a debugger is attached no matter which - // signal handler type is set - // We only check for this if we are not in the App Store environment - - BOOL debuggerIsAttached = NO; - if (self.appEnvironment != BITEnvironmentAppStore) { - if ([self isDebuggerAttached]) { - debuggerIsAttached = YES; - BITHockeyLogWarning(@"[HockeySDK] WARNING: Detecting crashes is NOT enabled due to running the app with a debugger attached."); - } - } - - if (!debuggerIsAttached) { - // Multiple exception handlers can be set, but we can only query the top level error handler (uncaught exception handler). - // - // To check if PLCrashReporter's error handler is successfully added, we compare the top - // level one that is set before and the one after PLCrashReporter sets up its own. - // - // With delayed processing we can then check if another error handler was set up afterwards - // and can show a debug warning log message, that the dev has to make sure the "newer" error handler - // doesn't exit the process itself, because then all subsequent handlers would never be invoked. - // - // Note: ANY error handler setup BEFORE HockeySDK initialization will not be processed! - - // get the current top level error handler - NSUncaughtExceptionHandler *initialHandler = NSGetUncaughtExceptionHandler(); - - // PLCrashReporter may only be initialized once. So make sure the developer - // can't break this - NSError *error = NULL; - -#if HOCKEYSDK_FEATURE_METRICS - [self configDefaultCrashCallback]; -#endif - // Set plCrashReporter callback which contains our default callback and potentially user defined callbacks - [self.plCrashReporter setCrashCallbacks:&plCrashCallbacks]; - - // Enable the Crash Reporter - if (![self.plCrashReporter enableCrashReporterAndReturnError: &error]) - BITHockeyLogError(@"[HockeySDK] ERROR: Could not enable crash reporter: %@", [error localizedDescription]); - - // get the new current top level error handler, which should now be the one from PLCrashReporter - NSUncaughtExceptionHandler *currentHandler = NSGetUncaughtExceptionHandler(); - - // do we have a new top level error handler? then we were successful - if (currentHandler && currentHandler != initialHandler) { - self.exceptionHandler = currentHandler; - - BITHockeyLogDebug(@"INFO: Exception handler successfully initialized."); - } else { - - // If we're running in a Xamarin Environment, the exception handler will be the one by the xamarin runtime, not ours. - // In other cases, this should never happen, theoretically only if NSSetUncaugtExceptionHandler() has some internal issues - BITHockeyLogError(@"[HockeySDK] ERROR: Exception handler could not be set. Make sure there is no other exception handler set up!"); - BITHockeyLogError(@"[HockeySDK] ERROR: If you are using the HockeySDK-Xamarin, this is expected behavior and you can ignore this message"); - } - - // Add the C++ uncaught exception handler, which is currently not handled by PLCrashReporter internally - [BITCrashUncaughtCXXExceptionHandlerManager addCXXExceptionHandler:uncaught_cxx_exception_handler]; - } - self.isSetup = YES; - }); - } - - if ([[NSUserDefaults standardUserDefaults] valueForKey:kBITAppDidReceiveLowMemoryNotification]) - self.didReceiveMemoryWarningInLastSession = [[NSUserDefaults standardUserDefaults] boolForKey:kBITAppDidReceiveLowMemoryNotification]; - - if (!self.didCrashInLastSession && self.isAppNotTerminatingCleanlyDetectionEnabled) { - BOOL didAppSwitchToBackgroundSafely = YES; - - if ([[NSUserDefaults standardUserDefaults] valueForKey:kBITAppWentIntoBackgroundSafely]) - didAppSwitchToBackgroundSafely = [[NSUserDefaults standardUserDefaults] boolForKey:kBITAppWentIntoBackgroundSafely]; - - if (!didAppSwitchToBackgroundSafely) { - BOOL considerReport = YES; - id strongDelegate = self.delegate; - if ([strongDelegate respondsToSelector:@selector(considerAppNotTerminatedCleanlyReportForCrashManager:)]) { - considerReport = [strongDelegate considerAppNotTerminatedCleanlyReportForCrashManager:self]; - } - - if (considerReport) { - BITHockeyLogVerbose(@"INFO: App kill detected, creating crash report."); - [self createCrashReportForAppKill]; - - self.didCrashInLastSession = YES; - } - } - } - -#if !defined (HOCKEYSDK_CONFIGURATION_ReleaseCrashOnlyExtensions) - if ([BITHockeyHelper applicationState] != BITApplicationStateActive) { - [self appEnteredForeground]; - } -#else - [self appEnteredForeground]; -#endif - - [[NSUserDefaults standardUserDefaults] setBool:NO forKey:kBITAppDidReceiveLowMemoryNotification]; - - [self triggerDelayedProcessing]; - BITHockeyLogVerbose(@"VERBOSE: CrashManager startManager has finished."); -} - -/** - * Creates a fake crash report because the app was killed while being in foreground - */ -- (void)createCrashReportForAppKill { - NSString *fakeReportUUID = bit_UUID(); - NSString *fakeReporterKey = bit_appAnonID(NO) ?: @"???"; - - NSString *fakeReportAppMarketingVersion = [[NSUserDefaults standardUserDefaults] objectForKey:kBITAppMarketingVersion]; - - NSString *fakeReportAppVersion = [[NSUserDefaults standardUserDefaults] objectForKey:kBITAppVersion]; - if (!fakeReportAppVersion) - return; - - NSString *fakeReportOSVersion = [[NSUserDefaults standardUserDefaults] objectForKey:kBITAppOSVersion] ?: [[UIDevice currentDevice] systemVersion]; - - NSString *fakeReportOSVersionString = fakeReportOSVersion; - NSString *fakeReportOSBuild = [[NSUserDefaults standardUserDefaults] objectForKey:kBITAppOSBuild] ?: [self osBuild]; - if (fakeReportOSBuild) { - fakeReportOSVersionString = [NSString stringWithFormat:@"%@ (%@)", fakeReportOSVersion, fakeReportOSBuild]; - } - - NSString *fakeReportAppBundleIdentifier = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"]; - NSString *fakeReportDeviceModel = [self getDevicePlatform] ?: @"Unknown"; - NSString *fakeReportAppUUIDs = [[NSUserDefaults standardUserDefaults] objectForKey:kBITAppUUIDs] ?: @""; - - NSString *fakeSignalName = kBITCrashKillSignal; - - NSMutableString *fakeReportString = [NSMutableString string]; - - [fakeReportString appendFormat:@"Incident Identifier: %@\n", fakeReportUUID]; - [fakeReportString appendFormat:@"CrashReporter Key: %@\n", fakeReporterKey]; - [fakeReportString appendFormat:@"Hardware Model: %@\n", fakeReportDeviceModel]; - [fakeReportString appendFormat:@"Identifier: %@\n", fakeReportAppBundleIdentifier]; - - NSString *fakeReportAppVersionString = fakeReportAppMarketingVersion ? [NSString stringWithFormat:@"%@ (%@)", fakeReportAppMarketingVersion, fakeReportAppVersion] : fakeReportAppVersion; - - [fakeReportString appendFormat:@"Version: %@\n", fakeReportAppVersionString]; - [fakeReportString appendString:@"Code Type: ARM\n"]; - [fakeReportString appendString:@"\n"]; - - NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; - NSDateFormatter *rfc3339Formatter = [[NSDateFormatter alloc] init]; - [rfc3339Formatter setLocale:enUSPOSIXLocale]; - [rfc3339Formatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"]; - [rfc3339Formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; - NSString *fakeCrashTimestamp = [rfc3339Formatter stringFromDate:[NSDate date]]; - - // we use the current date, since we don't know when the kill actually happened - [fakeReportString appendFormat:@"Date/Time: %@\n", fakeCrashTimestamp]; - [fakeReportString appendFormat:@"OS Version: %@\n", fakeReportOSVersionString]; - [fakeReportString appendString:@"Report Version: 104\n"]; - [fakeReportString appendString:@"\n"]; - [fakeReportString appendFormat:@"Exception Type: %@\n", fakeSignalName]; - [fakeReportString appendString:@"Exception Codes: 00000020 at 0x8badf00d\n"]; - [fakeReportString appendString:@"\n"]; - [fakeReportString appendString:@"Application Specific Information:\n"]; - [fakeReportString appendString:@"The application did not terminate cleanly but no crash occured."]; - if (self.didReceiveMemoryWarningInLastSession) { - [fakeReportString appendString:@" The app received at least one Low Memory Warning."]; - } - [fakeReportString appendString:@"\n\n"]; - - NSString *fakeReportFilename = [NSString stringWithFormat: @"%.0f", [NSDate timeIntervalSinceReferenceDate]]; - - NSError *error = nil; - - NSMutableDictionary *rootObj = [NSMutableDictionary dictionaryWithCapacity:2]; - [rootObj setObject:fakeReportUUID forKey:kBITFakeCrashUUID]; - if (fakeReportAppMarketingVersion) - [rootObj setObject:fakeReportAppMarketingVersion forKey:kBITFakeCrashAppMarketingVersion]; - [rootObj setObject:fakeReportAppVersion forKey:kBITFakeCrashAppVersion]; - [rootObj setObject:fakeReportAppBundleIdentifier forKey:kBITFakeCrashAppBundleIdentifier]; - [rootObj setObject:fakeReportOSVersion forKey:kBITFakeCrashOSVersion]; - [rootObj setObject:fakeReportDeviceModel forKey:kBITFakeCrashDeviceModel]; - [rootObj setObject:fakeReportAppUUIDs forKey:kBITFakeCrashAppBinaryUUID]; - [rootObj setObject:fakeReportString forKey:kBITFakeCrashReport]; - - self.lastSessionCrashDetails = [[BITCrashDetails alloc] initWithIncidentIdentifier:fakeReportUUID - reporterKey:fakeReporterKey - signal:fakeSignalName - exceptionName:nil - exceptionReason:nil - appStartTime:nil - crashTime:nil - osVersion:fakeReportOSVersion - osBuild:fakeReportOSBuild - appVersion:fakeReportAppMarketingVersion - appBuild:fakeReportAppVersion - appProcessIdentifier:[[NSProcessInfo processInfo] processIdentifier] - ]; - - NSData *plist = [NSPropertyListSerialization dataWithPropertyList:(id)rootObj - format:NSPropertyListBinaryFormat_v1_0 - options:0 - error:&error]; - if (plist) { - if ([plist writeToFile:[self.crashesDir stringByAppendingPathComponent:(NSString *)[fakeReportFilename stringByAppendingPathExtension:@"fake"]] atomically:YES]) { - [self storeMetaDataForCrashReportFilename:fakeReportFilename]; - } - } else { - BITHockeyLogError(@"ERROR: Writing fake crash report. %@", [error description]); - } -} - -/** - * Send all approved crash reports - * - * Gathers all collected data and constructs the XML structure and starts the sending process - */ -- (void)sendNextCrashReport { - NSError *error = NULL; - - self.crashIdenticalCurrentVersion = NO; - - if ([self.crashFiles count] == 0) - return; - - NSString *crashXML = nil; - BITHockeyAttachment *attachment = nil; - - // we start sending always with the oldest pending one - NSString *filename = [self.crashFiles objectAtIndex:0]; - NSString *attachmentFilename = filename; - NSString *cacheFilename = [filename lastPathComponent]; - NSData *crashData = [NSData dataWithContentsOfFile:filename]; - - if ([crashData length] > 0) { - BITPLCrashReport *report = nil; - NSString *crashUUID = @""; - NSString *installString = nil; - NSString *crashLogString = nil; - NSString *appBundleIdentifier = nil; - NSString *appBundleMarketingVersion = nil; - NSString *appBundleVersion = nil; - NSString *osVersion = nil; - NSString *deviceModel = nil; - NSString *appBinaryUUIDs = nil; - NSString *metaFilename = nil; - - NSPropertyListFormat format; - - if ([[cacheFilename pathExtension] isEqualToString:@"fake"]) { - NSDictionary *fakeReportDict = (NSDictionary *)[NSPropertyListSerialization - propertyListWithData:crashData - options:NSPropertyListMutableContainersAndLeaves - format:&format - error:&error]; - - crashLogString = [fakeReportDict objectForKey:kBITFakeCrashReport]; - crashUUID = [fakeReportDict objectForKey:kBITFakeCrashUUID]; - appBundleIdentifier = [fakeReportDict objectForKey:kBITFakeCrashAppBundleIdentifier]; - appBundleMarketingVersion = [fakeReportDict objectForKey:kBITFakeCrashAppMarketingVersion] ?: @""; - appBundleVersion = [fakeReportDict objectForKey:kBITFakeCrashAppVersion]; - appBinaryUUIDs = [fakeReportDict objectForKey:kBITFakeCrashAppBinaryUUID]; - deviceModel = [fakeReportDict objectForKey:kBITFakeCrashDeviceModel]; - osVersion = [fakeReportDict objectForKey:kBITFakeCrashOSVersion]; - - metaFilename = [cacheFilename stringByReplacingOccurrencesOfString:@".fake" withString:@".meta"]; - attachmentFilename = [attachmentFilename stringByReplacingOccurrencesOfString:@".fake" withString:@""]; - - if ([appBundleVersion compare:(NSString *)[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]] == NSOrderedSame) { - self.crashIdenticalCurrentVersion = YES; - } - - } else { - report = [[BITPLCrashReport alloc] initWithData:crashData error:&error]; - } - - if (report == nil && crashLogString == nil) { - BITHockeyLogWarning(@"WARNING: Could not parse crash report"); - // we cannot do anything with this report, so delete it - [self cleanCrashReportWithFilename:filename]; - // we don't continue with the next report here, even if there are to prevent calling sendCrashReports from itself again - // the next crash will be automatically send on the next app start/becoming active event - return; - } - - installString = bit_appAnonID(NO) ?: @""; - - if (report) { - if (report.uuidRef != NULL) { - crashUUID = (NSString *) CFBridgingRelease(CFUUIDCreateString(NULL, report.uuidRef)); - } - metaFilename = [cacheFilename stringByAppendingPathExtension:@"meta"]; - crashLogString = [BITCrashReportTextFormatter stringValueForCrashReport:report crashReporterKey:installString]; - appBundleIdentifier = report.applicationInfo.applicationIdentifier; - appBundleMarketingVersion = report.applicationInfo.applicationMarketingVersion ?: @""; - appBundleVersion = report.applicationInfo.applicationVersion; - osVersion = report.systemInfo.operatingSystemVersion; - deviceModel = [self getDevicePlatform]; - appBinaryUUIDs = [self extractAppUUIDs:report]; - if ([report.applicationInfo.applicationVersion compare:(NSString *)[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]] == NSOrderedSame) { - self.crashIdenticalCurrentVersion = YES; - } - } - - if ([report.applicationInfo.applicationVersion compare:(NSString *)[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]] == NSOrderedSame) { - self.crashIdenticalCurrentVersion = YES; - } - - NSString *username = @""; - NSString *useremail = @""; - NSString *userid = @""; - NSString *applicationLog = @""; - NSString *description = @""; - - NSData *plist = [NSData dataWithContentsOfFile:[self.crashesDir stringByAppendingPathComponent:metaFilename]]; - if (plist) { - NSDictionary *metaDict = (NSDictionary *)[NSPropertyListSerialization - propertyListWithData:plist - options:NSPropertyListMutableContainersAndLeaves - format:&format - error:&error]; - - username = [self stringValueFromKeychainForKey:[NSString stringWithFormat:@"%@.%@", attachmentFilename.lastPathComponent, kBITCrashMetaUserName]] ?: @""; - useremail = [self stringValueFromKeychainForKey:[NSString stringWithFormat:@"%@.%@", attachmentFilename.lastPathComponent, kBITCrashMetaUserEmail]] ?: @""; - userid = [self stringValueFromKeychainForKey:[NSString stringWithFormat:@"%@.%@", attachmentFilename.lastPathComponent, kBITCrashMetaUserID]] ?: @""; - applicationLog = [metaDict objectForKey:kBITCrashMetaApplicationLog] ?: @""; - description = [NSString stringWithContentsOfFile:[NSString stringWithFormat:@"%@.desc", [self.crashesDir stringByAppendingPathComponent: cacheFilename]] encoding:NSUTF8StringEncoding error:&error]; - attachment = [self attachmentForCrashReport:attachmentFilename]; - } else { - BITHockeyLogError(@"ERROR: Reading crash meta data. %@", error); - } - - if ([applicationLog length] > 0) { - if ([description length] > 0) { - description = [NSString stringWithFormat:@"%@\n\nLog:\n%@", description, applicationLog]; - } else { - description = [NSString stringWithFormat:@"Log:\n%@", applicationLog]; - } - } - - crashXML = [NSString stringWithFormat:@"%@%@%@%@%@%@%@%@%@%@%@%@", - [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleExecutable"], - appBinaryUUIDs, - appBundleIdentifier, - osVersion, - deviceModel, - [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"], - appBundleMarketingVersion, - appBundleVersion, - crashUUID, - [crashLogString stringByReplacingOccurrencesOfString:@"]]>" withString:@"]]" @"]]>" options:NSLiteralSearch range:NSMakeRange(0,crashLogString.length)], - userid, - username, - useremail, - installString, - [description stringByReplacingOccurrencesOfString:@"]]>" withString:@"]]" @"]]>" options:NSLiteralSearch range:NSMakeRange(0,description.length)]]; - - BITHockeyLogDebug(@"INFO: Sending crash reports:\n%@", crashXML); - [self sendCrashReportWithFilename:filename xml:crashXML attachment:attachment]; - } else { - // we cannot do anything with this report, so delete it - [self cleanCrashReportWithFilename:filename]; - } -} - -#pragma mark - Networking - -- (NSData *)postBodyWithXML:(NSString *)xml attachment:(BITHockeyAttachment *)attachment boundary:(NSString *)boundary { - NSMutableData *postBody = [NSMutableData data]; - - // [postBody appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]]; - [postBody appendData:[BITHockeyAppClient dataWithPostValue:BITHOCKEY_NAME - forKey:@"sdk" - boundary:boundary]]; - - [postBody appendData:[BITHockeyAppClient dataWithPostValue:BITHOCKEY_VERSION - forKey:@"sdk_version" - boundary:boundary]]; - - [postBody appendData:[BITHockeyAppClient dataWithPostValue:@"no" - forKey:@"feedbackEnabled" - boundary:boundary]]; - - [postBody appendData:[BITHockeyAppClient dataWithPostValue:[xml dataUsingEncoding:NSUTF8StringEncoding] - forKey:@"xml" - contentType:@"text/xml" - boundary:boundary - filename:@"crash.xml"]]; - - if (attachment && attachment.hockeyAttachmentData) { - NSString *attachmentFilename = attachment.filename; - if (!attachmentFilename) { - attachmentFilename = @"Attachment_0"; - } - [postBody appendData:[BITHockeyAppClient dataWithPostValue:attachment.hockeyAttachmentData - forKey:@"attachment0" - contentType:attachment.contentType - boundary:boundary - filename:attachmentFilename]]; - } - - [postBody appendData:(NSData *)[[NSString stringWithFormat:@"\r\n--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; - - return postBody; -} - -- (NSMutableURLRequest *)requestWithBoundary:(NSString *)boundary { - NSString *postCrashPath = [NSString stringWithFormat:@"api/2/apps/%@/crashes", self.encodedAppIdentifier]; - - NSMutableURLRequest *request = [self.hockeyAppClient requestWithMethod:@"POST" - path:postCrashPath - parameters:nil]; - - [request setCachePolicy: NSURLRequestReloadIgnoringLocalCacheData]; - [request setValue:@"HockeySDK/iOS" forHTTPHeaderField:@"User-Agent"]; - [request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"]; - - NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary]; - [request setValue:contentType forHTTPHeaderField:@"Content-type"]; - - return request; -} - -// process upload response -- (void)processUploadResultWithFilename:(NSString *)filename responseData:(NSData *)responseData statusCode:(NSInteger)statusCode error:(NSError *)error { - __block NSError *theError = error; - - dispatch_async(dispatch_get_main_queue(), ^{ - self.sendingInProgress = NO; - id strongDelegate = self.delegate; - if (nil == theError) { - if (nil == responseData || [responseData length] == 0) { - theError = [NSError errorWithDomain:kBITCrashErrorDomain - code:BITCrashAPIReceivedEmptyResponse - userInfo:@{ - NSLocalizedDescriptionKey: @"Sending failed with an empty response!" - } - ]; - } else if (statusCode >= 200 && statusCode < 400) { - [self cleanCrashReportWithFilename:filename]; - - // HockeyApp uses PList XML format - NSMutableDictionary *response = [NSPropertyListSerialization propertyListWithData:responseData - options:NSPropertyListMutableContainersAndLeaves - format:nil - error:&theError]; - BITHockeyLogDebug(@"INFO: Received API response: %@", response); - if ([strongDelegate respondsToSelector:@selector(crashManagerDidFinishSendingCrashReport:)]) { - [strongDelegate crashManagerDidFinishSendingCrashReport:self]; - } - - // only if sending the crash report went successfully, continue with the next one (if there are more) - [self sendNextCrashReport]; - } else if (statusCode == 400) { - [self cleanCrashReportWithFilename:filename]; - - theError = [NSError errorWithDomain:kBITCrashErrorDomain - code:BITCrashAPIAppVersionRejected - userInfo:@{ - NSLocalizedDescriptionKey: @"The server rejected receiving crash reports for this app version!" - } - ]; - } else { - theError = [NSError errorWithDomain:kBITCrashErrorDomain - code:BITCrashAPIErrorWithStatusCode - userInfo:@{ - NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Sending failed with status code: %li", (long)statusCode] - } - ]; - } - } - - if (theError) { - if ([strongDelegate respondsToSelector:@selector(crashManager:didFailWithError:)]) { - [strongDelegate crashManager:self didFailWithError:theError]; - } - - BITHockeyLogError(@"ERROR: %@", [theError localizedDescription]); - } - }); -} - -/** - * Send the XML data to the server - * - * Wraps the XML structure into a POST body and starts sending the data asynchronously - * - * @param xml The XML data that needs to be send to the server - */ -- (void)sendCrashReportWithFilename:(NSString *)filename xml:(NSString*)xml attachment:(BITHockeyAttachment *)attachment { - NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; - __block NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration]; - - NSURLRequest *request = [self requestWithBoundary:kBITHockeyAppClientBoundary]; - NSData *data = [self postBodyWithXML:xml attachment:attachment boundary:kBITHockeyAppClientBoundary]; - - if (request && data) { - __weak typeof (self) weakSelf = self; - NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request - fromData:data - completionHandler:^(NSData *responseData, NSURLResponse *response, NSError *error) { - typeof (self) strongSelf = weakSelf; - - [session finishTasksAndInvalidate]; - - NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*) response; - NSInteger statusCode = [httpResponse statusCode]; - [strongSelf processUploadResultWithFilename:filename responseData:responseData statusCode:statusCode error:error]; - }]; - - [uploadTask resume]; - } - id strongDelegate = self.delegate; - if ([strongDelegate respondsToSelector:@selector(crashManagerWillSendCrashReport:)]) { - [strongDelegate crashManagerWillSendCrashReport:self]; - } - - BITHockeyLogDebug(@"INFO: Sending crash reports started."); -} - -- (NSTimeInterval)timeintervalCrashInLastSessionOccured { - return self.timeIntervalCrashInLastSessionOccurred; -} - -@end - -#endif /* HOCKEYSDK_FEATURE_CRASH_REPORTER */ - diff --git a/submodules/HockeySDK-iOS/Classes/BITCrashManagerDelegate.h b/submodules/HockeySDK-iOS/Classes/BITCrashManagerDelegate.h deleted file mode 100644 index 786ead9d22..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITCrashManagerDelegate.h +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import - -@class BITCrashManager; -@class BITHockeyAttachment; - -/** - The `BITCrashManagerDelegate` formal protocol defines methods further configuring - the behaviour of `BITCrashManager`. - */ - -@protocol BITCrashManagerDelegate - -@optional - - -///----------------------------------------------------------------------------- -/// @name Additional meta data -///----------------------------------------------------------------------------- - -/** Return any log string based data the crash report being processed should contain - - @param crashManager The `BITCrashManager` instance invoking this delegate - @see attachmentForCrashManager: - @see BITHockeyManagerDelegate userNameForHockeyManager:componentManager: - @see BITHockeyManagerDelegate userEmailForHockeyManager:componentManager: - */ --(NSString *)applicationLogForCrashManager:(BITCrashManager *)crashManager; - - -/** Return a BITHockeyAttachment object providing an NSData object the crash report - being processed should contain - - Please limit your attachments to reasonable files to avoid high traffic costs for your users. - - Example implementation: - - - (BITHockeyAttachment *)attachmentForCrashManager:(BITCrashManager *)crashManager { - NSData *data = [NSData dataWithContentsOfURL:@"mydatafile"]; - - BITHockeyAttachment *attachment = [[BITHockeyAttachment alloc] initWithFilename:@"myfile.data" - hockeyAttachmentData:data - contentType:@"'application/octet-stream"]; - return attachment; - } - - @param crashManager The `BITCrashManager` instance invoking this delegate - @see BITHockeyAttachment - @see applicationLogForCrashManager: - @see BITHockeyManagerDelegate userNameForHockeyManager:componentManager: - @see BITHockeyManagerDelegate userEmailForHockeyManager:componentManager: - */ --(BITHockeyAttachment *)attachmentForCrashManager:(BITCrashManager *)crashManager; - - - -///----------------------------------------------------------------------------- -/// @name Alert -///----------------------------------------------------------------------------- - -/** Invoked before the user is asked to send a crash report, so you can do additional actions. - E.g. to make sure not to ask the user for an app rating :) - - @param crashManager The `BITCrashManager` instance invoking this delegate - */ --(void)crashManagerWillShowSubmitCrashReportAlert:(BITCrashManager *)crashManager; - - -/** Invoked after the user did choose _NOT_ to send a crash in the alert - - @param crashManager The `BITCrashManager` instance invoking this delegate - */ --(void)crashManagerWillCancelSendingCrashReport:(BITCrashManager *)crashManager; - - -/** Invoked after the user did choose to send crashes always in the alert - - @param crashManager The `BITCrashManager` instance invoking this delegate - */ --(void)crashManagerWillSendCrashReportsAlways:(BITCrashManager *)crashManager; - - -///----------------------------------------------------------------------------- -/// @name Networking -///----------------------------------------------------------------------------- - -/** Invoked right before sending crash reports will start - - @param crashManager The `BITCrashManager` instance invoking this delegate - */ -- (void)crashManagerWillSendCrashReport:(BITCrashManager *)crashManager; - -/** Invoked after sending crash reports failed - - @param crashManager The `BITCrashManager` instance invoking this delegate - @param error The error returned from the NSURLSession call or `kBITCrashErrorDomain` - with reason of type `BITCrashErrorReason`. - */ -- (void)crashManager:(BITCrashManager *)crashManager didFailWithError:(NSError *)error; - -/** Invoked after sending crash reports succeeded - - @param crashManager The `BITCrashManager` instance invoking this delegate - */ -- (void)crashManagerDidFinishSendingCrashReport:(BITCrashManager *)crashManager; - -///----------------------------------------------------------------------------- -/// @name Experimental -///----------------------------------------------------------------------------- - -/** Define if a report should be considered as a crash report - - Due to the risk, that these reports may be false positives, this delegates allows the - developer to influence which reports detected by the heuristic should actually be reported. - - The developer can use the following property to get more information about the crash scenario: - - `[BITCrashManager didReceiveMemoryWarningInLastSession]`: Did the app receive a low memory warning - - This allows only reports to be considered where at least one low memory warning notification was - received by the app to reduce to possibility of having false positives. - - @param crashManager The `BITCrashManager` instance invoking this delegate - @return `YES` if the heuristic based detected report should be reported, otherwise `NO` - @see `[BITCrashManager didReceiveMemoryWarningInLastSession]` - */ --(BOOL)considerAppNotTerminatedCleanlyReportForCrashManager:(BITCrashManager *)crashManager; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITCrashManagerPrivate.h b/submodules/HockeySDK-iOS/Classes/BITCrashManagerPrivate.h deleted file mode 100644 index 0add0c801d..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITCrashManagerPrivate.h +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2013-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_CRASH_REPORTER - -#import - -@class BITHockeyAppClient; - -@interface BITCrashManager () { -} - - -///----------------------------------------------------------------------------- -/// @name Delegate -///----------------------------------------------------------------------------- - -/** - Sets the optional `BITCrashManagerDelegate` delegate. - - The delegate is automatically set by using `[BITHockeyManager setDelegate:]`. You - should not need to set this delegate individually. - - @see `[BITHockeyManager setDelegate:]` - */ -@property (nonatomic, weak) id delegate; - -/** - * must be set - */ -@property (nonatomic, strong) BITHockeyAppClient *hockeyAppClient; - -@property (nonatomic) NSUncaughtExceptionHandler *exceptionHandler; - -@property (nonatomic, strong) NSFileManager *fileManager; - -@property (nonatomic, strong) BITPLCrashReporter *plCrashReporter; - -@property (nonatomic) NSString *lastCrashFilename; - -@property (nonatomic, copy, setter = setAlertViewHandler:) BITCustomAlertViewHandler alertViewHandler; - -@property (nonatomic, copy) NSString *crashesDir; - -#if HOCKEYSDK_FEATURE_AUTHENTICATOR - -// Only set via BITAuthenticator -@property (nonatomic, copy) NSString *installationIdentification; - -// Only set via BITAuthenticator -@property (nonatomic) BITAuthenticatorIdentificationType installationIdentificationType; - -// Only set via BITAuthenticator -@property (nonatomic) BOOL installationIdentified; - -#endif /* HOCKEYSDK_FEATURE_AUTHENTICATOR */ - -- (instancetype)initWithAppIdentifier:(NSString *)appIdentifier appEnvironment:(BITEnvironment)environment hockeyAppClient:(BITHockeyAppClient *)hockeyAppClient NS_DESIGNATED_INITIALIZER; - -- (void)cleanCrashReports; - -- (NSString *)userIDForCrashReport; -- (NSString *)userEmailForCrashReport; -- (NSString *)userNameForCrashReport; - -- (void)handleCrashReport; -- (BOOL)hasPendingCrashReport; -- (NSString *)firstNotApprovedCrashReport; - -- (void)persistUserProvidedMetaData:(BITCrashMetaData *)userProvidedMetaData; -- (BOOL)persistAttachment:(BITHockeyAttachment *)attachment withFilename:(NSString *)filename; - -- (BITHockeyAttachment *)attachmentForCrashReport:(NSString *)filename; - -- (void)invokeDelayedProcessing; -- (void)sendNextCrashReport; - -- (void)setLastCrashFilename:(NSString *)lastCrashFilename; - -- (void)leavingAppSafely; - -@end - - -#endif /* HOCKEYSDK_FEATURE_CRASH_REPORTER */ diff --git a/submodules/HockeySDK-iOS/Classes/BITCrashMetaData.h b/submodules/HockeySDK-iOS/Classes/BITCrashMetaData.h deleted file mode 100644 index 10508b5794..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITCrashMetaData.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import - - -/** - * This class provides properties that can be attached to a crash report via a custom alert view flow - */ -@interface BITCrashMetaData : NSObject - -/** - * User provided description that should be attached to the crash report as plain text - */ -@property (nonatomic, copy) NSString *userProvidedDescription; - -/** - * User name that should be attached to the crash report - */ -@property (nonatomic, copy) NSString *userName; - -/** - * User email that should be attached to the crash report - */ -@property (nonatomic, copy) NSString *userEmail; - -/** - * User ID that should be attached to the crash report - */ -@property (nonatomic, copy) NSString *userID; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITCrashMetaData.m b/submodules/HockeySDK-iOS/Classes/BITCrashMetaData.m deleted file mode 100644 index 2ec133dd65..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITCrashMetaData.m +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_CRASH_REPORTER - -#import "BITCrashMetaData.h" - -@implementation BITCrashMetaData - -@end - -#endif diff --git a/submodules/HockeySDK-iOS/Classes/BITCrashReportTextFormatter.h b/submodules/HockeySDK-iOS/Classes/BITCrashReportTextFormatter.h deleted file mode 100644 index c75d93c67f..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITCrashReportTextFormatter.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Authors: - * Landon Fuller - * Damian Morris - * Andreas Linde - * - * Copyright (c) 2008-2013 Plausible Labs Cooperative, Inc. - * Copyright (c) 2010 MOSO Corporation, Pty Ltd. - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - - -#import - -@class PLCrashReport; - - -// Dictionary keys for array elements returned by arrayOfAppUUIDsForCrashReport: -#ifndef kBITBinaryImageKeyUUID -#define kBITBinaryImageKeyUUID @"uuid" -#define kBITBinaryImageKeyArch @"arch" -#define kBITBinaryImageKeyType @"type" -#endif - - -/** - * HockeySDK Crash Reporter error domain - */ -typedef NS_ENUM (NSInteger, BITBinaryImageType) { - /** - * App binary - */ - BITBinaryImageTypeAppBinary, - /** - * App provided framework - */ - BITBinaryImageTypeAppFramework, - /** - * Image not related to the app - */ - BITBinaryImageTypeOther -}; - - -@interface BITCrashReportTextFormatter : NSObject { -} - -+ (NSString *)stringValueForCrashReport:(PLCrashReport *)report crashReporterKey:(NSString *)crashReporterKey; -+ (NSArray *)arrayOfAppUUIDsForCrashReport:(PLCrashReport *)report; -+ (NSString *)bit_archNameFromCPUType:(uint64_t)cpuType subType:(uint64_t)subType; -+ (BITBinaryImageType)bit_imageTypeForImagePath:(NSString *)imagePath processPath:(NSString *)processPath; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITCrashReportTextFormatter.m b/submodules/HockeySDK-iOS/Classes/BITCrashReportTextFormatter.m deleted file mode 100644 index 4c896802e6..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITCrashReportTextFormatter.m +++ /dev/null @@ -1,928 +0,0 @@ -/* - * Authors: - * Landon Fuller - * Damian Morris - * Andreas Linde - * - * Copyright (c) 2008-2013 Plausible Labs Cooperative, Inc. - * Copyright (c) 2010 MOSO Corporation, Pty Ltd. - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "HockeySDK.h" -#import "HockeySDKPrivate.h" - -#if HOCKEYSDK_FEATURE_CRASH_REPORTER - -#import - -#import -#import -#import -#import -#import - -#if defined(__OBJC2__) -#define SEL_NAME_SECT "__objc_methname" -#else -#define SEL_NAME_SECT "__cstring" -#endif - -#import "BITCrashReportTextFormatter.h" - -/* - * XXX: The ARM64 CPU type, and ARM_V7S and ARM_V8 Mach-O CPU subtypes are not - * defined in the Mac OS X 10.8 headers. - */ -#ifndef CPU_SUBTYPE_ARM_V7S -# define CPU_SUBTYPE_ARM_V7S 11 -#endif - -#ifndef CPU_TYPE_ARM64 -#define CPU_TYPE_ARM64 (CPU_TYPE_ARM | CPU_ARCH_ABI64) -#endif - -#ifndef CPU_SUBTYPE_ARM_V8 -# define CPU_SUBTYPE_ARM_V8 13 -#endif - -/** - * Sort PLCrashReportBinaryImageInfo instances by their starting address. - */ -static NSInteger bit_binaryImageSort(id binary1, id binary2, void *__unused context) { - uint64_t addr1 = [binary1 imageBaseAddress]; - uint64_t addr2 = [binary2 imageBaseAddress]; - - if (addr1 < addr2) - return NSOrderedAscending; - else if (addr1 > addr2) - return NSOrderedDescending; - else - return NSOrderedSame; -} - -/** - * Validates that the given @a string terminates prior to @a limit. - */ -static const char *safer_string_read (const char *string, const char *limit) { - const char *p = string; - do { - if (p >= limit || p+1 >= limit) { - return NULL; - } - p++; - } while (*p != '\0'); - - return string; -} - -/* - * The relativeAddress should be ` - `, extracted from the crash report's thread - * and binary image list. - * - * For the (architecture-specific) registers to attempt, see: - * http://sealiesoftware.com/blog/archive/2008/09/22/objc_explain_So_you_crashed_in_objc_msgSend.html - */ -static const char *findSEL (const char *imageName, NSString *imageUUID, uint64_t relativeAddress) { - unsigned int images_count = _dyld_image_count(); - for (unsigned int i = 0; i < images_count; ++i) { - intptr_t slide = _dyld_get_image_vmaddr_slide(i); - const struct mach_header *header = _dyld_get_image_header(i); - const struct mach_header_64 *header64 = (const struct mach_header_64 *) header; - const char *name = _dyld_get_image_name(i); - - /* Image disappeared? */ - if (name == NULL || header == NULL) - continue; - - /* Check if this is the correct image. If we were being even more careful, we'd check the LC_UUID */ - if (strcmp(name, imageName) != 0) - continue; - - /* Determine whether this is a 64-bit or 32-bit Mach-O file */ - BOOL m64 = NO; - if (header->magic == MH_MAGIC_64) - m64 = YES; - - NSString *uuidString = nil; - const uint8_t *command; - uint32_t ncmds; - - if (m64) { - command = (const uint8_t *)(header64 + 1); - ncmds = header64->ncmds; - } else { - command = (const uint8_t *)(header + 1); - ncmds = header->ncmds; - } - for (uint32_t idx = 0; idx < ncmds; ++idx) { - const struct load_command *load_command = (const struct load_command *)command; - if (load_command->cmd == LC_UUID) { - const struct uuid_command *uuid_command = (const struct uuid_command *)command; - const uint8_t *uuid = uuid_command->uuid; - uuidString = [[NSString stringWithFormat:@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", - uuid[0], uuid[1], uuid[2], uuid[3], - uuid[4], uuid[5], uuid[6], uuid[7], - uuid[8], uuid[9], uuid[10], uuid[11], - uuid[12], uuid[13], uuid[14], uuid[15]] - lowercaseString]; - break; - } else { - command += load_command->cmdsize; - } - } - - // Check if this is the correct image by comparing the UUIDs - if (!uuidString || ![uuidString isEqualToString:imageUUID]) - continue; - - /* Fetch the __objc_methname section */ - const char *methname_sect; - uint64_t methname_sect_size; - if (m64) { - methname_sect = getsectdatafromheader_64(header64, SEG_TEXT, SEL_NAME_SECT, &methname_sect_size); - } else { - uint32_t meth_size_32; - methname_sect = getsectdatafromheader(header, SEG_TEXT, SEL_NAME_SECT, &meth_size_32); - methname_sect_size = meth_size_32; - } - - /* Apply the slide, as per getsectdatafromheader(3) */ - methname_sect += slide; - - if (methname_sect == NULL) { - return NULL; - } - - /* Calculate the target address within this image, and verify that it is within __objc_methname */ - const char *target = ((const char *)header) + relativeAddress; - const char *limit = methname_sect + methname_sect_size; - if (target < methname_sect || target >= limit) { - return NULL; - } - - /* Read the actual method name */ - return safer_string_read(target, limit); - } - - return NULL; -} - -/** - * Formats PLCrashReport data as human-readable text. - */ -@implementation BITCrashReportTextFormatter - -static NSString *const BITXamarinStackTraceDelimiter = @"Xamarin Exception Stack:"; - -/** - * Formats the provided @a report as human-readable text in the given @a textFormat, and return - * the formatted result as a string. - * - * @param report The report to format. - * @param crashReporterKey The crash reporter key. - * - * @return Returns the formatted result on success, or nil if an error occurs. - */ -+ (NSString *)stringValueForCrashReport:(BITPLCrashReport *)report crashReporterKey:(NSString *)crashReporterKey { - NSMutableString* text = [NSMutableString string]; - boolean_t lp64 = true; // quiesce GCC uninitialized value warning - - /* Header */ - - /* Map to apple style OS name */ - NSString *osName; - switch (report.systemInfo.operatingSystem) { - case PLCrashReportOperatingSystemMacOSX: - osName = @"Mac OS X"; - break; - case PLCrashReportOperatingSystemiPhoneOS: - osName = @"iPhone OS"; - break; - case PLCrashReportOperatingSystemiPhoneSimulator: - osName = @"Mac OS X"; - break; - default: - osName = [NSString stringWithFormat: @"Unknown (%d)", report.systemInfo.operatingSystem]; - break; - } - - /* Map to Apple-style code type, and mark whether architecture is LP64 (64-bit) */ - NSString *codeType = nil; - { - /* Attempt to derive the code type from the binary images */ - for (BITPLCrashReportBinaryImageInfo *image in report.images) { - /* Skip images with no specified type */ - if (image.codeType == nil) - continue; - - /* Skip unknown encodings */ - if (image.codeType.typeEncoding != PLCrashReportProcessorTypeEncodingMach) - continue; - - switch (image.codeType.type) { - case CPU_TYPE_ARM: - codeType = @"ARM"; - lp64 = false; - break; - - case CPU_TYPE_ARM64: - codeType = @"ARM-64"; - lp64 = true; - break; - - case CPU_TYPE_X86: - codeType = @"X86"; - lp64 = false; - break; - - case CPU_TYPE_X86_64: - codeType = @"X86-64"; - lp64 = true; - break; - - case CPU_TYPE_POWERPC: - codeType = @"PPC"; - lp64 = false; - break; - - default: - // Do nothing, handled below. - break; - } - - /* Stop immediately if code type was discovered */ - if (codeType != nil) - break; - } -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - /* If we were unable to determine the code type, fall back on the legacy architecture value. */ - if (codeType == nil) { - switch (report.systemInfo.architecture) { - case PLCrashReportArchitectureARMv6: - case PLCrashReportArchitectureARMv7: - codeType = @"ARM"; - lp64 = false; - break; - case PLCrashReportArchitectureX86_32: - codeType = @"X86"; - lp64 = false; - break; - case PLCrashReportArchitectureX86_64: - codeType = @"X86-64"; - lp64 = true; - break; - case PLCrashReportArchitecturePPC: - codeType = @"PPC"; - lp64 = false; - break; - default: - codeType = [NSString stringWithFormat: @"Unknown (%d)", report.systemInfo.architecture]; - lp64 = true; - break; - } - } -#pragma GCC diagnostic pop - } - - { - NSString *reporterKey = @"???"; - if (crashReporterKey && [crashReporterKey length] > 0) - reporterKey = crashReporterKey; - - NSString *hardwareModel = @"???"; - if (report.hasMachineInfo && report.machineInfo.modelName != nil) - hardwareModel = report.machineInfo.modelName; - - NSString *incidentIdentifier = @"???"; - if (report.uuidRef != NULL) { - incidentIdentifier = (NSString *) CFBridgingRelease(CFUUIDCreateString(NULL, report.uuidRef)); - } - - [text appendFormat: @"Incident Identifier: %@\n", incidentIdentifier]; - [text appendFormat: @"CrashReporter Key: %@\n", reporterKey]; - [text appendFormat: @"Hardware Model: %@\n", hardwareModel]; - } - - /* Application and process info */ - { - NSString *unknownString = @"???"; - - NSString *processName = unknownString; - NSString *processId = unknownString; - NSString *processPath = unknownString; - NSString *parentProcessName = unknownString; - NSString *parentProcessId = unknownString; - - /* Process information was not available in earlier crash report versions */ - if (report.hasProcessInfo) { - /* Process Name */ - if (report.processInfo.processName != nil) - processName = report.processInfo.processName; - - /* PID */ - processId = [@(report.processInfo.processID) stringValue]; - - /* Process Path */ - if (report.processInfo.processPath != nil) { - processPath = report.processInfo.processPath; - - /* Remove username from the path */ -#if TARGET_OS_SIMULATOR - processPath = [self anonymizedPathFromPath:processPath]; -#endif - } - - /* Parent Process Name */ - if (report.processInfo.parentProcessName != nil) - parentProcessName = report.processInfo.parentProcessName; - - /* Parent Process ID */ - parentProcessId = [@(report.processInfo.parentProcessID) stringValue]; - } - - [text appendFormat: @"Process: %@ [%@]\n", processName, processId]; - [text appendFormat: @"Path: %@\n", processPath]; - [text appendFormat: @"Identifier: %@\n", report.applicationInfo.applicationIdentifier]; - - NSString *marketingVersion = report.applicationInfo.applicationMarketingVersion; - NSString *appVersion = report.applicationInfo.applicationVersion; - NSString *versionString = marketingVersion ? [NSString stringWithFormat:@"%@ (%@)", marketingVersion, appVersion] : appVersion; - - [text appendFormat: @"Version: %@\n", versionString]; - [text appendFormat: @"Code Type: %@\n", codeType]; - [text appendFormat: @"Parent Process: %@ [%@]\n", parentProcessName, parentProcessId]; - } - - [text appendString: @"\n"]; - - NSString *xamarinTrace; - NSString *exceptionReason; - - /* System info */ - { - NSString *osBuild = @"???"; - if (report.systemInfo.operatingSystemBuild != nil) - osBuild = report.systemInfo.operatingSystemBuild; - - NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; - NSDateFormatter *rfc3339Formatter = [[NSDateFormatter alloc] init]; - [rfc3339Formatter setLocale:enUSPOSIXLocale]; - [rfc3339Formatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"]; - [rfc3339Formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; - - [text appendFormat: @"Date/Time: %@\n", [rfc3339Formatter stringFromDate:report.systemInfo.timestamp]]; - if ([report.processInfo respondsToSelector:@selector(processStartTime)]) { - if (report.systemInfo.timestamp && report.processInfo.processStartTime) { - [text appendFormat: @"Launch Time: %@\n", [rfc3339Formatter stringFromDate:report.processInfo.processStartTime]]; - } - } - [text appendFormat: @"OS Version: %@ %@ (%@)\n", osName, report.systemInfo.operatingSystemVersion, osBuild]; - - // Check if exception data contains xamarin stacktrace in order to determine report version - if (report.hasExceptionInfo) { - exceptionReason = report.exceptionInfo.exceptionReason; - NSInteger xamarinTracePosition = [exceptionReason rangeOfString:BITXamarinStackTraceDelimiter].location; - if (xamarinTracePosition != NSNotFound) { - xamarinTrace = [exceptionReason substringFromIndex:xamarinTracePosition]; - xamarinTrace = [xamarinTrace stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; - xamarinTrace = [xamarinTrace stringByReplacingOccurrencesOfString:@"<---\n\n--->" withString:@"<---\n--->"]; - exceptionReason = [exceptionReason substringToIndex:xamarinTracePosition]; - exceptionReason = [exceptionReason stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; - } - } - NSString *reportVersion = (xamarinTrace) ? @"104-Xamarin" : @"104"; - [text appendFormat: @"Report Version: %@\n", reportVersion]; - } - - [text appendString: @"\n"]; - - /* Exception code */ - [text appendFormat: @"Exception Type: %@\n", report.signalInfo.name]; - [text appendFormat: @"Exception Codes: %@ at 0x%" PRIx64 "\n", report.signalInfo.code, report.signalInfo.address]; - - for (BITPLCrashReportThreadInfo *thread in report.threads) { - if (thread.crashed) { - [text appendFormat: @"Crashed Thread: %ld\n", (long) thread.threadNumber]; - break; - } - } - - [text appendString: @"\n"]; - - BITPLCrashReportThreadInfo *crashed_thread = nil; - for (BITPLCrashReportThreadInfo *thread in report.threads) { - if (thread.crashed) { - crashed_thread = thread; - break; - } - } - - /* Uncaught Exception */ - if (report.hasExceptionInfo) { - [text appendFormat: @"Application Specific Information:\n"]; - [text appendFormat: @"*** Terminating app due to uncaught exception '%@', reason: '%@'\n", - report.exceptionInfo.exceptionName, exceptionReason]; - [text appendString: @"\n"]; - - /* Xamarin Exception */ - if (xamarinTrace) { - [text appendFormat:@"%@\n", xamarinTrace]; - [text appendString: @"\n"]; - } - - } else if (crashed_thread != nil) { - // try to find the selector in case this was a crash in obj_msgSend - // we search this whether the crash happened in obj_msgSend or not since we don't have the symbol! - - NSString *foundSelector = nil; - - // search the registers value for the current arch -#if TARGET_OS_SIMULATOR - if (lp64) { - foundSelector = [[self class] selectorForRegisterWithName:@"rsi" ofThread:crashed_thread report:report]; - if (foundSelector == NULL) - foundSelector = [[self class] selectorForRegisterWithName:@"rdx" ofThread:crashed_thread report:report]; - } else { - foundSelector = [[self class] selectorForRegisterWithName:@"ecx" ofThread:crashed_thread report:report]; - } -#else - if (lp64) { - foundSelector = [[self class] selectorForRegisterWithName:@"x1" ofThread:crashed_thread report:report]; - } else { - foundSelector = [[self class] selectorForRegisterWithName:@"r1" ofThread:crashed_thread report:report]; - if (foundSelector == NULL) - foundSelector = [[self class] selectorForRegisterWithName:@"r2" ofThread:crashed_thread report:report]; - } -#endif - - if (foundSelector) { - [text appendFormat: @"Application Specific Information:\n"]; - [text appendFormat: @"Selector name found in current argument registers: %@\n", foundSelector]; - [text appendString: @"\n"]; - } - } - - /* If an exception stack trace is available, output an Apple-compatible backtrace. */ - if (report.exceptionInfo != nil && report.exceptionInfo.stackFrames != nil && [report.exceptionInfo.stackFrames count] > 0) { - BITPLCrashReportExceptionInfo *exception = report.exceptionInfo; - - /* Create the header. */ - [text appendString: @"Last Exception Backtrace:\n"]; - - /* Write out the frames. In raw reports, Apple writes this out as a simple list of PCs. In the minimally - * post-processed report, Apple writes this out as full frame entries. We use the latter format. */ - for (NSUInteger frame_idx = 0; frame_idx < [exception.stackFrames count]; frame_idx++) { - BITPLCrashReportStackFrameInfo *frameInfo = exception.stackFrames[frame_idx]; - [text appendString: [[self class] bit_formatStackFrame: frameInfo frameIndex: frame_idx report: report lp64: lp64]]; - } - [text appendString: @"\n"]; - } - - /* Threads */ - NSInteger maxThreadNum = 0; - for (BITPLCrashReportThreadInfo *thread in report.threads) { - if (thread.crashed) { - [text appendFormat: @"Thread %ld Crashed:\n", (long) thread.threadNumber]; - } else { - [text appendFormat: @"Thread %ld:\n", (long) thread.threadNumber]; - } - for (NSUInteger frame_idx = 0; frame_idx < [thread.stackFrames count]; frame_idx++) { - BITPLCrashReportStackFrameInfo *frameInfo = thread.stackFrames[frame_idx]; - [text appendString:[[self class] bit_formatStackFrame:frameInfo frameIndex:frame_idx report:report lp64:lp64]]; - } - [text appendString: @"\n"]; - - /* Track the highest thread number */ - maxThreadNum = MAX(maxThreadNum, thread.threadNumber); - } - - /* Registers */ - if (crashed_thread != nil) { - [text appendFormat: @"Thread %ld crashed with %@ Thread State:\n", (long) crashed_thread.threadNumber, codeType]; - - int regColumn = 0; - for (BITPLCrashReportRegisterInfo *reg in crashed_thread.registers) { - NSString *reg_fmt; - - /* Use 32-bit or 64-bit fixed width format for the register values */ - if (lp64) - reg_fmt = @"%6s: 0x%016" PRIx64 " "; - else - reg_fmt = @"%6s: 0x%08" PRIx64 " "; - - /* Remap register names to match Apple's crash reports */ - NSString *regName = reg.registerName; - if (report.machineInfo != nil && report.machineInfo.processorInfo.typeEncoding == PLCrashReportProcessorTypeEncodingMach) { - BITPLCrashReportProcessorInfo *pinfo = report.machineInfo.processorInfo; - cpu_type_t arch_type = (cpu_type_t)(pinfo.type & ~CPU_ARCH_MASK); - - /* Apple uses 'ip' rather than 'r12' on ARM */ - if (arch_type == CPU_TYPE_ARM && [regName isEqual: @"r12"]) { - regName = @"ip"; - } - } - [text appendFormat: reg_fmt, [regName UTF8String], reg.registerValue]; - - regColumn++; - if (regColumn == 4) { - [text appendString: @"\n"]; - regColumn = 0; - } - } - - if (regColumn != 0) - [text appendString: @"\n"]; - - [text appendString: @"\n"]; - } - - /* Images. The iPhone crash report format sorts these in ascending order, by the base address */ - [text appendString: @"Binary Images:\n"]; - NSMutableArray *addedImagesBaseAddresses = @[].mutableCopy; - for (BITPLCrashReportBinaryImageInfo *imageInfo in [report.images sortedArrayUsingFunction: bit_binaryImageSort context: nil]) { - // Make sure we don't add duplicates - if ([addedImagesBaseAddresses containsObject:@(imageInfo.imageBaseAddress)]) { - continue; - } else { - [addedImagesBaseAddresses addObject:@(imageInfo.imageBaseAddress)]; - } - - NSString *uuid; - /* Fetch the UUID if it exists */ - if (imageInfo.hasImageUUID) - uuid = imageInfo.imageUUID; - else - uuid = @"???"; - - /* Determine the architecture string */ - NSString *archName = [[self class] bit_archNameFromImageInfo:imageInfo]; - - /* Determine if this is the main executable or an app specific framework*/ - NSString *binaryDesignator = @" "; - BITBinaryImageType imageType = [[self class] bit_imageTypeForImagePath:imageInfo.imageName - processPath:report.processInfo.processPath]; - if (imageType != BITBinaryImageTypeOther) { - binaryDesignator = @"+"; - } - - /* base_address - terminating_address [designator]file_name arch file_path */ - NSString *fmt = nil; - if (lp64) { - fmt = @"%18#" PRIx64 " - %18#" PRIx64 " %@%@ %@ <%@> %@\n"; - } else { - fmt = @"%10#" PRIx64 " - %10#" PRIx64 " %@%@ %@ <%@> %@\n"; - } - - /* Remove username from the image path */ - NSString *imageName = @""; - if (imageInfo.imageName && [imageInfo.imageName length] > 0) { -#if TARGET_OS_SIMULATOR - imageName = [imageInfo.imageName stringByAbbreviatingWithTildeInPath]; -#else - imageName = imageInfo.imageName; -#endif - } -#if TARGET_OS_SIMULATOR - imageName = [self anonymizedPathFromPath:imageName]; -#endif - [text appendFormat: fmt, - imageInfo.imageBaseAddress, - imageInfo.imageBaseAddress + (MAX(1U, imageInfo.imageSize) - 1), // The Apple format uses an inclusive range - binaryDesignator, - [imageInfo.imageName lastPathComponent], - archName, - uuid, - imageName]; - } - - - return text; -} - -/** - * Return the selector string of a given register name - * - * @param regName The name of the register to use for getting the address - * @param thread The crashed thread - * @param report The crash report. - * - * @return The selector as a C string or NULL if no selector was found - */ -+ (NSString *)selectorForRegisterWithName:(NSString *)regName ofThread:(BITPLCrashReportThreadInfo *)thread report:(BITPLCrashReport *)report { - // get the address for the register - uint64_t regAddress = 0; - - for (BITPLCrashReportRegisterInfo *reg in thread.registers) { - if ([reg.registerName isEqualToString:regName]) { - regAddress = reg.registerValue; - break; - } - } - - if (regAddress == 0) - return nil; - - BITPLCrashReportBinaryImageInfo *imageForRegAddress = [report imageForAddress:regAddress]; - if (imageForRegAddress) { - // get the SEL - const char *foundSelector = findSEL([imageForRegAddress.imageName UTF8String], imageForRegAddress.imageUUID, regAddress - (uint64_t)imageForRegAddress.imageBaseAddress); - - if (foundSelector != NULL) { - return [NSString stringWithUTF8String:foundSelector]; - } - } - - return nil; -} - - -/** - * Returns an array of app UUIDs and their architecture - * As a dictionary for each element - * - * @param report The report to format. - * - * @return Returns the formatted result on success, or nil if an error occurs. - */ -+ (NSArray *)arrayOfAppUUIDsForCrashReport:(BITPLCrashReport *)report { - NSMutableArray* appUUIDs = [NSMutableArray array]; - - /* Images. The iPhone crash report format sorts these in ascending order, by the base address */ - for (BITPLCrashReportBinaryImageInfo *imageInfo in [report.images sortedArrayUsingFunction: bit_binaryImageSort context: nil]) { - NSString *uuid; - /* Fetch the UUID if it exists */ - uuid = imageInfo.hasImageUUID ? imageInfo.imageUUID : @"???"; - - /* Determine the architecture string */ - NSString *archName = [[self class] bit_archNameFromImageInfo:imageInfo]; - - /* Determine if this is the app executable or app specific framework */ - BITBinaryImageType imageType = [[self class] bit_imageTypeForImagePath:imageInfo.imageName - processPath:report.processInfo.processPath]; - if (imageType != BITBinaryImageTypeOther) { - NSString *imageTypeString; - - if (imageType == BITBinaryImageTypeAppBinary) { - imageTypeString = @"app"; - } else { - imageTypeString = @"framework"; - } - - [appUUIDs addObject:@{kBITBinaryImageKeyUUID: uuid, - kBITBinaryImageKeyArch: archName, - kBITBinaryImageKeyType: imageTypeString} - ]; - } - } - - return appUUIDs; -} - -/* Determine if in binary image is the app executable or app specific framework */ -+ (BITBinaryImageType)bit_imageTypeForImagePath:(NSString *)imagePath processPath:(NSString *)processPath { - if (!imagePath || !processPath) { - return BITBinaryImageTypeOther; - } - BITBinaryImageType imageType = BITBinaryImageTypeOther; - - NSString *standardizedImagePath = [[imagePath stringByStandardizingPath] lowercaseString]; - NSString *lowercaseImagePath = [imagePath lowercaseString]; - NSString *lowercaseProcessPath = [processPath lowercaseString]; - - NSRange appRange = [standardizedImagePath rangeOfString: @".app/"]; - - // Exclude iOS swift dylibs. These are provided as part of the app binary by Xcode for now, but we never get a dSYM for those. - NSRange swiftLibRange = [standardizedImagePath rangeOfString:@"frameworks/libswift"]; - BOOL dylibSuffix = [standardizedImagePath hasSuffix:@".dylib"]; - - if (appRange.location != NSNotFound && !(swiftLibRange.location != NSNotFound && dylibSuffix)) { - NSString *appBundleContentsPath = [standardizedImagePath substringToIndex:appRange.location + 5]; - - if ([standardizedImagePath isEqual: lowercaseProcessPath] || - // Fix issue with iOS 8 `stringByStandardizingPath` removing leading `/private` path (when not running in the debugger or simulator only) - [lowercaseImagePath hasPrefix:lowercaseProcessPath]) { - imageType = BITBinaryImageTypeAppBinary; - } else if ([standardizedImagePath hasPrefix:appBundleContentsPath] || - // Fix issue with iOS 8 `stringByStandardizingPath` removing leading `/private` path (when not running in the debugger or simulator only) - [lowercaseImagePath hasPrefix:appBundleContentsPath]) { - imageType = BITBinaryImageTypeAppFramework; - } - } - - return imageType; -} - -+ (NSString *)bit_archNameFromImageInfo:(BITPLCrashReportBinaryImageInfo *)imageInfo -{ - NSString *archName = @"???"; - if (imageInfo.codeType != nil && imageInfo.codeType.typeEncoding == PLCrashReportProcessorTypeEncodingMach) { - archName = [BITCrashReportTextFormatter bit_archNameFromCPUType:imageInfo.codeType.type subType:imageInfo.codeType.subtype]; - } - - return archName; -} - -+ (NSString *)bit_archNameFromCPUType:(uint64_t)cpuType subType:(uint64_t)subType { - NSString *archName = @"???"; - switch (cpuType) { - case CPU_TYPE_ARM: - /* Apple includes subtype for ARM binaries. */ - switch (subType) { - case CPU_SUBTYPE_ARM_V6: - archName = @"armv6"; - break; - - case CPU_SUBTYPE_ARM_V7: - archName = @"armv7"; - break; - - case CPU_SUBTYPE_ARM_V7S: - archName = @"armv7s"; - break; - - default: - archName = @"arm-unknown"; - break; - } - break; - - case CPU_TYPE_ARM64: - /* Apple includes subtype for ARM64 binaries. */ - switch (subType) { - case CPU_SUBTYPE_ARM_ALL: - archName = @"arm64"; - break; - - case CPU_SUBTYPE_ARM_V8: - archName = @"arm64"; - break; - - default: - archName = @"arm64-unknown"; - break; - } - break; - - case CPU_TYPE_X86: - archName = @"i386"; - break; - - case CPU_TYPE_X86_64: - archName = @"x86_64"; - break; - - case CPU_TYPE_POWERPC: - archName = @"powerpc"; - break; - - default: - // Use the default archName value (initialized above). - break; - } - - return archName; -} - - -/** - * Format a stack frame for display in a thread backtrace. - * - * @param frameInfo The stack frame to format - * @param frameIndex The frame's index - * @param report The report from which this frame was acquired. - * @param lp64 If YES, the report was generated by an LP64 system. - * - * @return Returns a formatted frame line. - */ -+ (NSString *)bit_formatStackFrame: (BITPLCrashReportStackFrameInfo *) frameInfo - frameIndex: (NSUInteger) frameIndex - report: (BITPLCrashReport *) report - lp64: (boolean_t) lp64 -{ - /* Base image address containing instrumentation pointer, offset of the IP from that base - * address, and the associated image name */ - uint64_t baseAddress = 0x0; - uint64_t pcOffset = 0x0; - NSString *imageName = @"\?\?\?"; - NSString *symbolString = nil; - - BITPLCrashReportBinaryImageInfo *imageInfo = [report imageForAddress: frameInfo.instructionPointer]; - if (imageInfo != nil) { - imageName = [imageInfo.imageName lastPathComponent]; - baseAddress = imageInfo.imageBaseAddress; - pcOffset = frameInfo.instructionPointer - imageInfo.imageBaseAddress; - } - - /* Make sure UTF8/16 characters are handled correctly */ - NSInteger offset = 0; - NSUInteger index = 0; - for (index = 0; index < [imageName length]; index++) { - NSRange range = [imageName rangeOfComposedCharacterSequenceAtIndex:index]; - if (range.length > 1) { - offset += range.length - 1; - index += range.length - 1; - } - if (index > 32) { - imageName = [NSString stringWithFormat:@"%@... ", [imageName substringToIndex:index - 1]]; - index += 3; - break; - } - } - if (index-offset < 36) { - imageName = [imageName stringByPaddingToLength:(NSUInteger)(36 + offset) withString:@" " startingAtIndex:0]; - } - - /* If symbol info is available, the format used in Apple's reports is Sym + OffsetFromSym. Otherwise, - * the format used is imageBaseAddress + offsetToIP */ - BITBinaryImageType imageType = [[self class] bit_imageTypeForImagePath:imageInfo.imageName - processPath:report.processInfo.processPath]; - if (frameInfo.symbolInfo != nil && imageType == BITBinaryImageTypeOther) { - NSString *symbolName = frameInfo.symbolInfo.symbolName; - - /* Apple strips the _ symbol prefix in their reports. Only OS X makes use of an - * underscore symbol prefix by default. */ - if ([symbolName rangeOfString: @"_"].location == 0 && [symbolName length] > 1) { - switch (report.systemInfo.operatingSystem) { - case PLCrashReportOperatingSystemMacOSX: - case PLCrashReportOperatingSystemiPhoneOS: - case PLCrashReportOperatingSystemiPhoneSimulator: - symbolName = [symbolName substringFromIndex: 1]; - break; - - default: - BITHockeyLogDebug(@"Symbol prefix rules are unknown for this OS!"); - break; - } - } - - - uint64_t symOffset = frameInfo.instructionPointer - frameInfo.symbolInfo.startAddress; - symbolString = [NSString stringWithFormat: @"%@ + %" PRId64, symbolName, symOffset]; - } else { - symbolString = [NSString stringWithFormat: @"0x%" PRIx64 " + %" PRId64, baseAddress, pcOffset]; - } - - /* Note that width specifiers are ignored for %@, but work for C strings. - * UTF-8 is not correctly handled with %s (it depends on the system encoding), but - * UTF-16 is supported via %S, so we use it here */ - return [NSString stringWithFormat: @"%-4ld%-35S 0x%0*" PRIx64 " %@\n", - (long) frameIndex, - (const uint16_t *)[imageName cStringUsingEncoding: NSUTF16StringEncoding], - lp64 ? 16 : 8, frameInfo.instructionPointer, - symbolString]; -} - -/** - * Remove the user's name from a crash's process path. - * This is only necessary when sending crashes from the simulator as the path - * then contains the username of the Mac the simulator is running on. - * - * @param path A string containing the username. - * - * @return An anonymized string where the real username is replaced by "USER" - */ -+ (NSString *)anonymizedPathFromPath:(NSString *)path { - - NSString *anonymizedProcessPath = [NSString string]; - - if (([path length] > 0) && [path hasPrefix:@"/Users/"]) { - NSError *error = nil; - NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"(/Users/[^/]+/)" options:0 error:&error]; - anonymizedProcessPath = [regex stringByReplacingMatchesInString:path options:0 range:NSMakeRange(0, [path length]) withTemplate:@"/Users/USER/"]; - if (error) { - BITHockeyLogError(@"ERROR: String replacing failed - %@", error.localizedDescription); - } - } - else if(([path length] > 0) && (![path containsString:@"Users"])) { - return path; - } - return anonymizedProcessPath; -} - -@end - -#endif /* HOCKEYSDK_FEATURE_CRASH_REPORTER */ diff --git a/submodules/HockeySDK-iOS/Classes/BITCrashReportTextFormatterPrivate.h b/submodules/HockeySDK-iOS/Classes/BITCrashReportTextFormatterPrivate.h deleted file mode 100644 index 5b53c4f231..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITCrashReportTextFormatterPrivate.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// BITCrashReportTextFormatterPrivate.h -// HockeySDK -// -// Created by Lukas Spieß on 27/01/16. -// -// - -#import "BITCrashReportTextFormatter.h" - -#ifndef BITCrashReportTextFormatterPrivate_h -#define BITCrashReportTextFormatterPrivate_h - -@interface BITCrashReportTextFormatter () - -+ (NSString *)anonymizedPathFromPath:(NSString *)path; - -@end - -#endif /* BITCrashReportTextFormatterPrivate_h */ diff --git a/submodules/HockeySDK-iOS/Classes/BITData.h b/submodules/HockeySDK-iOS/Classes/BITData.h deleted file mode 100644 index 05147d6b95..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITData.h +++ /dev/null @@ -1,13 +0,0 @@ -#import "BITBase.h" -@class BITTelemetryData; - -@interface BITData : BITBase - -@property (nonatomic, strong) BITTelemetryData *baseData; - -- (instancetype)initWithCoder:(NSCoder *)coder; - -- (void)encodeWithCoder:(NSCoder *)coder; - - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITData.m b/submodules/HockeySDK-iOS/Classes/BITData.m deleted file mode 100644 index 958e6ac9aa..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITData.m +++ /dev/null @@ -1,39 +0,0 @@ -#import "BITData.h" -#import "BITHockeyLogger.h" - -/// Data contract class for type Data. -@implementation BITData - -/// -/// Adds all members of this class to a dictionary -/// @returns dictionary to which the members of this class will be added. -/// -- (NSDictionary *)serializeToDictionary { - NSMutableDictionary *dict = [super serializeToDictionary].mutableCopy; - NSDictionary *baseDataDict = [self.baseData serializeToDictionary]; - if ([NSJSONSerialization isValidJSONObject:baseDataDict]) { - [dict setObject:baseDataDict forKey:@"baseData"]; - } else { - BITHockeyLogError(@"[HockeySDK] Some of the telemetry data was not NSJSONSerialization compatible and could not be serialized!"); - } - return dict; -} - -#pragma mark - NSCoding - -- (instancetype)initWithCoder:(NSCoder *)coder { - self = [super initWithCoder:coder]; - if(self) { - _baseData = [coder decodeObjectForKey:@"self.baseData"]; - } - - return self; -} - -- (void)encodeWithCoder:(NSCoder *)coder { - [super encodeWithCoder:coder]; - [coder encodeObject:self.baseData forKey:@"self.baseData"]; -} - - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITDevice.h b/submodules/HockeySDK-iOS/Classes/BITDevice.h deleted file mode 100755 index 95fa4d52af..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITDevice.h +++ /dev/null @@ -1,27 +0,0 @@ -#import "BITTelemetryObject.h" - -@interface BITDevice : BITTelemetryObject - -@property (nonatomic, copy) NSString *deviceId; -@property (nonatomic, copy) NSString *ip; -@property (nonatomic, copy) NSString *language; -@property (nonatomic, copy) NSString *locale; -@property (nonatomic, copy) NSString *machineName; -@property (nonatomic, copy) NSString *model; -@property (nonatomic, copy) NSString *network; -@property (nonatomic, copy) NSString *networkName; -@property (nonatomic, copy) NSString *oemName; -@property (nonatomic, copy) NSString *os; -@property (nonatomic, copy) NSString *osVersion; -@property (nonatomic, copy) NSString *roleInstance; -@property (nonatomic, copy) NSString *roleName; -@property (nonatomic, copy) NSString *screenResolution; -@property (nonatomic, copy) NSString *type; -@property (nonatomic, copy) NSString *vmName; - -- (instancetype)initWithCoder:(NSCoder *)coder; - -- (void)encodeWithCoder:(NSCoder *)coder; - - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITDevice.m b/submodules/HockeySDK-iOS/Classes/BITDevice.m deleted file mode 100755 index 54eb987a74..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITDevice.m +++ /dev/null @@ -1,110 +0,0 @@ -#import "BITDevice.h" - -/// Data contract class for type Device. -@implementation BITDevice - -/// -/// Adds all members of this class to a dictionary -/// @returns dictionary to which the members of this class will be added. -/// -- (NSDictionary *)serializeToDictionary { - NSMutableDictionary *dict = [super serializeToDictionary].mutableCopy; - if (self.deviceId != nil) { - [dict setObject:self.deviceId forKey:@"ai.device.id"]; - } - if (self.ip != nil) { - [dict setObject:self.ip forKey:@"ai.device.ip"]; - } - if (self.language != nil) { - [dict setObject:self.language forKey:@"ai.device.language"]; - } - if (self.locale != nil) { - [dict setObject:self.locale forKey:@"ai.device.locale"]; - } - if (self.model != nil) { - [dict setObject:self.model forKey:@"ai.device.model"]; - } - if (self.network != nil) { - [dict setObject:self.network forKey:@"ai.device.network"]; - } - if(self.networkName != nil) { - [dict setObject:self.networkName forKey:@"ai.device.networkName"]; - } - if (self.oemName != nil) { - [dict setObject:self.oemName forKey:@"ai.device.oemName"]; - } - if (self.os != nil) { - [dict setObject:self.os forKey:@"ai.device.os"]; - } - if (self.osVersion != nil) { - [dict setObject:self.osVersion forKey:@"ai.device.osVersion"]; - } - if (self.roleInstance != nil) { - [dict setObject:self.roleInstance forKey:@"ai.device.roleInstance"]; - } - if (self.roleName != nil) { - [dict setObject:self.roleName forKey:@"ai.device.roleName"]; - } - if (self.screenResolution != nil) { - [dict setObject:self.screenResolution forKey:@"ai.device.screenResolution"]; - } - if (self.type != nil) { - [dict setObject:self.type forKey:@"ai.device.type"]; - } - if (self.machineName != nil) { - [dict setObject:self.machineName forKey:@"ai.device.machineName"]; - } - if(self.vmName != nil) { - [dict setObject:self.vmName forKey:@"ai.device.vmName"]; - } - return dict; -} - -#pragma mark - NSCoding - -- (instancetype)initWithCoder:(NSCoder *)coder { - self = [super init]; - if(self) { - _deviceId = [coder decodeObjectForKey:@"self.deviceId"]; - _ip = [coder decodeObjectForKey:@"self.ip"]; - _language = [coder decodeObjectForKey:@"self.language"]; - _locale = [coder decodeObjectForKey:@"self.locale"]; - _model = [coder decodeObjectForKey:@"self.model"]; - _network = [coder decodeObjectForKey:@"self.network"]; - _oemName = [coder decodeObjectForKey:@"self.oemName"]; - _os = [coder decodeObjectForKey:@"self.os"]; - _osVersion = [coder decodeObjectForKey:@"self.osVersion"]; - _roleInstance = [coder decodeObjectForKey:@"self.roleInstance"]; - _roleName = [coder decodeObjectForKey:@"self.roleName"]; - _screenResolution = [coder decodeObjectForKey:@"self.screenResolution"]; - _type = [coder decodeObjectForKey:@"self.type"]; - _machineName = [coder decodeObjectForKey:@"self.machineName"]; - _networkName = [coder decodeObjectForKey:@"self.networkName"]; - _vmName = [coder decodeObjectForKey:@"self.vmName"]; - } - - return self; -} - -- (void)encodeWithCoder:(NSCoder *)coder { - [super encodeWithCoder:coder]; - [coder encodeObject:self.deviceId forKey:@"self.deviceId"]; - [coder encodeObject:self.ip forKey:@"self.ip"]; - [coder encodeObject:self.language forKey:@"self.language"]; - [coder encodeObject:self.locale forKey:@"self.locale"]; - [coder encodeObject:self.model forKey:@"self.model"]; - [coder encodeObject:self.network forKey:@"self.network"]; - [coder encodeObject:self.networkName forKey:@"self.networkName"]; - [coder encodeObject:self.oemName forKey:@"self.oemName"]; - [coder encodeObject:self.os forKey:@"self.os"]; - [coder encodeObject:self.osVersion forKey:@"self.osVersion"]; - [coder encodeObject:self.roleInstance forKey:@"self.roleInstance"]; - [coder encodeObject:self.roleName forKey:@"self.roleName"]; - [coder encodeObject:self.screenResolution forKey:@"self.screenResolution"]; - [coder encodeObject:self.type forKey:@"self.type"]; - [coder encodeObject:self.machineName forKey:@"self.machineName"]; - [coder encodeObject:self.vmName forKey:@"self.vmName"]; -} - - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITDomain.h b/submodules/HockeySDK-iOS/Classes/BITDomain.h deleted file mode 100644 index c20cd5a71f..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITDomain.h +++ /dev/null @@ -1,5 +0,0 @@ -#import "BITTelemetryData.h" - -@interface BITDomain : BITTelemetryData - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITDomain.m b/submodules/HockeySDK-iOS/Classes/BITDomain.m deleted file mode 100644 index d1d743af04..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITDomain.m +++ /dev/null @@ -1,45 +0,0 @@ -#import "BITDomain.h" -/// Data contract class for type Domain. -@implementation BITDomain -@synthesize envelopeTypeName = _envelopeTypeName; -@synthesize dataTypeName = _dataTypeName; -@synthesize properties = _properties; - -/// Initializes a new instance of the class. -- (instancetype)init { - if ((self = [super init])) { - _envelopeTypeName = @"Microsoft.ApplicationInsights.Domain"; - _dataTypeName = @"Domain"; - } - return self; -} - -/// -/// Adds all members of this class to a dictionary -/// @returns dictionary to which the members of this class will be added. -/// -- (NSDictionary *)serializeToDictionary { - NSMutableDictionary *dict = [super serializeToDictionary].mutableCopy; - return dict; -} - -#pragma mark - NSCoding - -- (instancetype)initWithCoder:(NSCoder *)coder { - self = [super initWithCoder:coder]; - if(self) { - _envelopeTypeName = (NSString *)[coder decodeObjectForKey:@"_envelopeTypeName"]; - _dataTypeName = (NSString *)[coder decodeObjectForKey:@"_dataTypeName"]; - } - - return self; -} - -- (void)encodeWithCoder:(NSCoder *)coder { - [super encodeWithCoder:coder]; - [coder encodeObject:self.envelopeTypeName forKey:@"_envelopeTypeName"]; - [coder encodeObject:self.dataTypeName forKey:@"_dataTypeName"]; -} - - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITEnvelope.h b/submodules/HockeySDK-iOS/Classes/BITEnvelope.h deleted file mode 100644 index 5237132215..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITEnvelope.h +++ /dev/null @@ -1,23 +0,0 @@ -#import "BITTelemetryObject.h" -@class BITBase; - -@interface BITEnvelope : BITTelemetryObject - -@property (nonatomic, copy) NSNumber *version; -@property (nonatomic, copy) NSString *name; -@property (nonatomic, copy) NSString *time; -@property (nonatomic, copy) NSNumber *sampleRate; -@property (nonatomic, copy) NSString *seq; -@property (nonatomic, copy) NSString *iKey; -@property (nonatomic, copy) NSNumber *flags; -@property (nonatomic, copy) NSString *deviceId; -@property (nonatomic, copy) NSString *os; -@property (nonatomic, copy) NSString *osVer; -@property (nonatomic, copy) NSString *appId; -@property (nonatomic, copy) NSString *appVer; -@property (nonatomic, copy) NSString *userId; -@property (nonatomic, strong) NSDictionary *tags; -@property (nonatomic, strong) BITBase *data; - - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITEnvelope.m b/submodules/HockeySDK-iOS/Classes/BITEnvelope.m deleted file mode 100644 index b833f58f89..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITEnvelope.m +++ /dev/null @@ -1,120 +0,0 @@ -#import "BITEnvelope.h" -#import "BITData.h" -#import "BITHockeyLogger.h" - -/// Data contract class for type Envelope. -@implementation BITEnvelope - -/// Initializes a new instance of the class. -- (instancetype)init { - if((self = [super init])) { - _version = @1; - _sampleRate = @100.0; - _tags = [NSDictionary dictionary]; - } - return self; -} - -/// -/// Adds all members of this class to a dictionary -/// @returns dictionary to which the members of this class will be added. -/// -- (NSDictionary *)serializeToDictionary { - NSMutableDictionary *dict = [super serializeToDictionary].mutableCopy; - if(self.version != nil) { - [dict setObject:self.version forKey:@"ver"]; - } - if(self.name != nil) { - [dict setObject:self.name forKey:@"name"]; - } - if(self.time != nil) { - [dict setObject:self.time forKey:@"time"]; - } - if(self.sampleRate != nil) { - [dict setObject:self.sampleRate forKey:@"sampleRate"]; - } - if(self.seq != nil) { - [dict setObject:self.seq forKey:@"seq"]; - } - if(self.iKey != nil) { - [dict setObject:self.iKey forKey:@"iKey"]; - } - if(self.flags != nil) { - [dict setObject:self.flags forKey:@"flags"]; - } - if(self.deviceId != nil) { - [dict setObject:self.deviceId forKey:@"deviceId"]; - } - if(self.os != nil) { - [dict setObject:self.os forKey:@"os"]; - } - if(self.osVer != nil) { - [dict setObject:self.osVer forKey:@"osVer"]; - } - if(self.appId != nil) { - [dict setObject:self.appId forKey:@"appId"]; - } - if(self.appVer != nil) { - [dict setObject:self.appVer forKey:@"appVer"]; - } - if(self.userId != nil) { - [dict setObject:self.userId forKey:@"userId"]; - } - if(self.tags != nil) { - [dict setObject:self.tags forKey:@"tags"]; - } - - NSDictionary *dataDict = [self.data serializeToDictionary]; - if ([NSJSONSerialization isValidJSONObject:dataDict]) { - [dict setObject:dataDict forKey:@"data"]; - } else { - BITHockeyLogError(@"[HockeySDK] Some of the telemetry data was not NSJSONSerialization compatible and could not be serialized!"); - } - return dict; -} - -#pragma mark - NSCoding - -- (instancetype)initWithCoder:(NSCoder *)coder { - self = [super init]; - if(self) { - _version = [coder decodeObjectForKey:@"self.version"]; - _name = [coder decodeObjectForKey:@"self.name"]; - _time = [coder decodeObjectForKey:@"self.time"]; - _sampleRate = [coder decodeObjectForKey:@"self.sampleRate"]; - _seq = [coder decodeObjectForKey:@"self.seq"]; - _iKey = [coder decodeObjectForKey:@"self.iKey"]; - _flags = [coder decodeObjectForKey:@"self.flags"]; - _deviceId = [coder decodeObjectForKey:@"self.deviceId"]; - _os = [coder decodeObjectForKey:@"self.os"]; - _osVer = [coder decodeObjectForKey:@"self.osVer"]; - _appId = [coder decodeObjectForKey:@"self.appId"]; - _appVer = [coder decodeObjectForKey:@"self.appVer"]; - _userId = [coder decodeObjectForKey:@"self.userId"]; - _tags = [coder decodeObjectForKey:@"self.tags"]; - _data = [coder decodeObjectForKey:@"self.data"]; - } - - return self; -} - -- (void)encodeWithCoder:(NSCoder *)coder { - [coder encodeObject:self.version forKey:@"self.version"]; - [coder encodeObject:self.name forKey:@"self.name"]; - [coder encodeObject:self.time forKey:@"self.time"]; - [coder encodeObject:self.sampleRate forKey:@"self.sampleRate"]; - [coder encodeObject:self.seq forKey:@"self.seq"]; - [coder encodeObject:self.iKey forKey:@"self.iKey"]; - [coder encodeObject:self.flags forKey:@"self.flags"]; - [coder encodeObject:self.deviceId forKey:@"self.deviceId"]; - [coder encodeObject:self.os forKey:@"self.os"]; - [coder encodeObject:self.osVer forKey:@"self.osVer"]; - [coder encodeObject:self.appId forKey:@"self.appId"]; - [coder encodeObject:self.appVer forKey:@"self.appVer"]; - [coder encodeObject:self.userId forKey:@"self.userId"]; - [coder encodeObject:self.tags forKey:@"self.tags"]; - [coder encodeObject:self.data forKey:@"self.data"]; -} - - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITEventData.h b/submodules/HockeySDK-iOS/Classes/BITEventData.h deleted file mode 100644 index 50bb133330..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITEventData.h +++ /dev/null @@ -1,9 +0,0 @@ -#import "BITDomain.h" - -@interface BITEventData : BITDomain - -@property (nonatomic, copy, readonly) NSString *envelopeTypeName; -@property (nonatomic, copy, readonly) NSString *dataTypeName; -@property (nonatomic, strong) NSDictionary *measurements; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITEventData.m b/submodules/HockeySDK-iOS/Classes/BITEventData.m deleted file mode 100644 index b5ef6f07c9..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITEventData.m +++ /dev/null @@ -1,66 +0,0 @@ -#import "BITEventData.h" - -/// Data contract class for type EventData. -@implementation BITEventData -@synthesize envelopeTypeName = _envelopeTypeName; -@synthesize dataTypeName = _dataTypeName; -@synthesize version = _version; -@synthesize properties = _properties; -@synthesize measurements = _measurements; - -/// Initializes a new instance of the class. -- (instancetype)init { - if ((self = [super init])) { - _envelopeTypeName = @"Microsoft.ApplicationInsights.Event"; - _dataTypeName = @"EventData"; - _version = @2; - _properties = [NSDictionary new]; - _measurements = [NSDictionary new]; - } - return self; -} - -/// -/// Adds all members of this class to a dictionary -/// @returns dictionary to which the members of this class will be added. -/// -- (NSDictionary *)serializeToDictionary { - NSMutableDictionary *dict = [super serializeToDictionary].mutableCopy; - if (self.name != nil) { - [dict setObject:self.name forKey:@"name"]; - } - if (self.properties !=nil) { - [dict setObject:self.properties forKey:@"properties"]; - } - if (self.measurements) { - [dict setObject:self.measurements forKey:@"measurements"]; - } - - return dict; -} - -#pragma mark - NSCoding - -- (instancetype)initWithCoder:(NSCoder *)coder { - self = [super initWithCoder:coder]; - if(self) { - _envelopeTypeName = [coder decodeObjectForKey:@"self.envelopeTypeName"]; - _dataTypeName = [coder decodeObjectForKey:@"self.dataTypeName"]; - _version = (NSNumber *)[coder decodeObjectForKey:@"self.version"]; - _properties = (NSDictionary *)[coder decodeObjectForKey:@"self.properties"]; - _measurements = [coder decodeObjectForKey:@"self.measurements"]; - } - return self; -} - -- (void)encodeWithCoder:(NSCoder *)coder { - [super encodeWithCoder:coder]; - [coder encodeObject:self.envelopeTypeName forKey:@"self.envelopeTypeName"]; - [coder encodeObject:self.dataTypeName forKey:@"self.dataTypeName"]; - [coder encodeObject:self.version forKey:@"self.version"]; - [coder encodeObject:self.properties forKey:@"self.properties"]; - [coder encodeObject:self.measurements forKey:@"self.measurements"]; -} - - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITFeedbackActivity.h b/submodules/HockeySDK-iOS/Classes/BITFeedbackActivity.h deleted file mode 100644 index 44c08a985c..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITFeedbackActivity.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import - -#import "BITFeedbackComposeViewControllerDelegate.h" - -/** - UIActivity subclass allowing to use the feedback interface to share content with the developer - - This activity can be added into an UIActivityViewController and it will use the activity data - objects to prefill the content of `BITFeedbackComposeViewController`. - - This can be useful if you present some data that users can not only share but also - report back to the developer because they have some problems, e.g. webcams not working - any more. - - The activity provide a default title and image that can be further customized - via `customActivityImage` and `customActivityTitle`. - - */ - -@interface BITFeedbackActivity : UIActivity - -///----------------------------------------------------------------------------- -/// @name BITFeedbackActivity customisation -///----------------------------------------------------------------------------- - - -/** - Define the image shown when using `BITFeedbackActivity` - - If not set a default icon is being used. - - @see customActivityTitle - */ -@property (nonatomic, strong) UIImage *customActivityImage; - - -/** - Define the title shown when using `BITFeedbackActivity` - - If not set, a default string is shown by using the apps name - and adding the localized string "Feedback" to it. - - @see customActivityImage - */ -@property (nonatomic, copy) NSString *customActivityTitle; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITFeedbackActivity.m b/submodules/HockeySDK-iOS/Classes/BITFeedbackActivity.m deleted file mode 100644 index 08e1775062..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITFeedbackActivity.m +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_FEEDBACK - -#import "HockeySDKPrivate.h" - -#import "BITFeedbackActivity.h" - -#import "BITHockeyHelper.h" -#import "BITFeedbackManagerPrivate.h" - -#import "BITHockeyBaseManagerPrivate.h" -#import "BITHockeyAttachment.h" - - -@interface BITFeedbackActivity() - -@property (nonatomic, strong) NSMutableArray *items; -@property (nonatomic, strong, readwrite) UIViewController *activityViewController; - -@end - - -@implementation BITFeedbackActivity - -@synthesize activityViewController = _activityViewController; - -#pragma mark - NSObject - -- (instancetype)init { - if ((self = [super init])) { - _customActivityImage = nil; - _customActivityTitle = nil; - - _items = [NSMutableArray array]; - } - - return self; -} - - - -#pragma mark - UIActivity - -- (NSString *)activityType { - return @"UIActivityTypePostToHockeySDKFeedback"; -} - -- (NSString *)activityTitle { - if (self.customActivityTitle) - return self.customActivityTitle; - - NSString *appName = bit_appName(BITHockeyLocalizedString(@"HockeyFeedbackActivityAppPlaceholder")); - - return [NSString stringWithFormat:BITHockeyLocalizedString(@"HockeyFeedbackActivityButtonTitle"), appName]; -} - -- (UIImage *)activityImage { - if (self.customActivityImage) - return self.customActivityImage; - - return bit_imageNamed(@"feedbackActivity.png", BITHOCKEYSDK_BUNDLE); -} - - -- (BOOL)canPerformWithActivityItems:(NSArray *)activityItems { - if ([BITHockeyManager sharedHockeyManager].disableFeedbackManager) return NO; - - for (UIActivityItemProvider *item in activityItems) { - if ([item isKindOfClass:[NSString class]]) { - return YES; - } else if ([item isKindOfClass:[UIImage class]]) { - return YES; - } else if ([item isKindOfClass:[NSData class]]) { - return YES; - } else if ([item isKindOfClass:[BITHockeyAttachment class]]) { - return YES; - } else if ([item isKindOfClass:[NSURL class]]) { - return YES; - } - } - return NO; -} - -- (void)prepareWithActivityItems:(NSArray *)activityItems { - for (id item in activityItems) { - if ([item isKindOfClass:[NSString class]] || - [item isKindOfClass:[UIImage class]] || - [item isKindOfClass:[NSData class]] || - [item isKindOfClass:[BITHockeyAttachment class]] || - [item isKindOfClass:[NSURL class]]) { - [self.items addObject:item]; - } else { - BITHockeyLogWarning(@"WARNING: Unknown item type %@", item); - } - } -} - -- (UIViewController *)activityViewController { - if (!_activityViewController) { - // TODO: return compose controller with activity content added - BITFeedbackManager *manager = [BITHockeyManager sharedHockeyManager].feedbackManager; - - BITFeedbackComposeViewController *composeViewController = [manager feedbackComposeViewController]; - composeViewController.delegate = self; - [composeViewController prepareWithItems:self.items]; - - _activityViewController = [manager customNavigationControllerWithRootViewController:composeViewController - presentationStyle:UIModalPresentationFormSheet]; - _activityViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; - } - return _activityViewController; -} - -- (void)feedbackComposeViewController:(BITFeedbackComposeViewController *) __unused composeViewController didFinishWithResult:(BITFeedbackComposeResult)composeResult { - [self activityDidFinish:composeResult == BITFeedbackComposeResultSubmitted]; -} - - -@end - -#endif /* HOCKEYSDK_FEATURE_FEEDBACK */ diff --git a/submodules/HockeySDK-iOS/Classes/BITFeedbackComposeViewController.h b/submodules/HockeySDK-iOS/Classes/BITFeedbackComposeViewController.h deleted file mode 100644 index ec58531894..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITFeedbackComposeViewController.h +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - - -#import - -#import "BITFeedbackComposeViewControllerDelegate.h" - -/** - View controller allowing the user to write and send new feedback - - To add this view controller to your own app and push it onto a navigation stack, - don't create the instance yourself, but use the following code to get a correct instance: - - [[BITHockeyManager sharedHockeyManager].feedbackManager feedbackComposeViewController] - - To show it modally, use the following code instead: - - [[BITHockeyManager sharedHockeyManager].feedbackManager showFeedbackComposeView] - - */ - -@interface BITFeedbackComposeViewController : UIViewController - - -///----------------------------------------------------------------------------- -/// @name Delegate -///----------------------------------------------------------------------------- - - -/** - Sets the `BITFeedbackComposeViewControllerDelegate` delegate. - - The delegate is automatically set by using `[BITHockeyManager setDelegate:]`. You - should not need to set this delegate individually. - - @see [BITHockeyManager setDelegate:] - */ -@property (nonatomic, weak) id delegate; - - -///----------------------------------------------------------------------------- -/// @name Presetting content -///----------------------------------------------------------------------------- - - -/** - Don't show the option to add images from the photo library - - This is helpful if your application is landscape only, since the system UI for - selecting an image from the photo library is portrait only - */ -@property (nonatomic) BOOL hideImageAttachmentButton; - -/** - An array of data objects that should be used to prefill the compose view content - - The following data object classes are currently supported: - - NSString - - NSURL - - UIImage - - NSData - - `BITHockeyAttachment` - - These are automatically concatenated to one text string, while any images and NSData - objects are added as attachments to the feedback. - - @param items Array of data objects to prefill the feedback text message. - */ -- (void)prepareWithItems:(NSArray *)items; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITFeedbackComposeViewController.m b/submodules/HockeySDK-iOS/Classes/BITFeedbackComposeViewController.m deleted file mode 100644 index 49dfc46699..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITFeedbackComposeViewController.m +++ /dev/null @@ -1,690 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_FEEDBACK - -#import "HockeySDKPrivate.h" - -#import "BITFeedbackManagerPrivate.h" -#import "BITFeedbackMessageAttachment.h" -#import "BITFeedbackComposeViewController.h" -#import "BITFeedbackUserDataViewController.h" - -#import "BITHockeyBaseManagerPrivate.h" - -#import "BITHockeyHelper.h" - -#import "BITImageAnnotationViewController.h" -#import "BITHockeyAttachment.h" - -@interface BITFeedbackComposeViewController () { -} - -@property (nonatomic, weak) BITFeedbackManager *manager; -@property (nonatomic, strong) UITextView *textView; -@property (nonatomic, strong) UIView *contentViewContainer; -@property (nonatomic, strong) UIScrollView *attachmentScrollView; -@property (nonatomic, strong) NSMutableArray *attachmentScrollViewImageViews; - -@property (nonatomic, strong) UIButton *addPhotoButton; - -@property (nonatomic, copy) NSString *text; - -@property (nonatomic, strong) NSMutableArray *attachments; -@property (nonatomic, strong) NSMutableArray *imageAttachments; - -@property (nonatomic, strong) UIView *textAccessoryView; -@property (nonatomic) NSInteger selectedAttachmentIndex; -@property (nonatomic, strong) UITapGestureRecognizer *tapRecognizer; - -@property (nonatomic) BOOL blockUserDataScreen; -@property (nonatomic) BOOL actionSheetVisible; - -/** - * Workaround for UIImagePickerController bug. - * The statusBar shows up when the UIImagePickerController opens. - * The status bar does not disappear again when the UIImagePickerController is dismissed. - * Therefore store the state when UIImagePickerController is shown and restore when viewWillAppear gets called. - */ -@property (nonatomic, strong) NSNumber *isStatusBarHiddenBeforeShowingPhotoPicker; - -@end - - -@implementation BITFeedbackComposeViewController - - -#pragma mark - NSObject - -- (instancetype)init { - self = [super init]; - if (self) { - _blockUserDataScreen = NO; - _actionSheetVisible = NO; - _delegate = nil; - _manager = [BITHockeyManager sharedHockeyManager].feedbackManager; - _attachments = [NSMutableArray new]; - _imageAttachments = [NSMutableArray new]; - _attachmentScrollViewImageViews = [NSMutableArray new]; - _tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(scrollViewTapped:)]; - [_attachmentScrollView addGestureRecognizer:self.tapRecognizer]; - - _text = nil; - } - - return self; -} - - -#pragma mark - Public - -- (void)prepareWithItems:(NSArray *)items { - for (id item in items) { - if ([item isKindOfClass:[NSString class]]) { - self.text = [(self.text ? self.text : @"") stringByAppendingFormat:@"%@%@", (self.text ? @" " : @""), item]; - } else if ([item isKindOfClass:[NSURL class]]) { - self.text = [(self.text ? self.text : @"") stringByAppendingFormat:@"%@%@", (self.text ? @" " : @""), [(NSURL *)item absoluteString]]; - } else if ([item isKindOfClass:[UIImage class]]) { - UIImage *image = item; - BITFeedbackMessageAttachment *attachment = [BITFeedbackMessageAttachment attachmentWithData:UIImageJPEGRepresentation(image, (CGFloat)0.7) contentType:@"image/jpeg"]; - attachment.originalFilename = [NSString stringWithFormat:@"Image_%li.jpg", (unsigned long)[self.attachments count]]; - [self.attachments addObject:attachment]; - [self.imageAttachments addObject:attachment]; - } else if ([item isKindOfClass:[NSData class]]) { - BITFeedbackMessageAttachment *attachment = [BITFeedbackMessageAttachment attachmentWithData:item contentType:@"application/octet-stream"]; - attachment.originalFilename = [NSString stringWithFormat:@"Attachment_%li.data", (unsigned long)[self.attachments count]]; - [self.attachments addObject:attachment]; - } else if ([item isKindOfClass:[BITHockeyAttachment class]]) { - BITHockeyAttachment *sourceAttachment = (BITHockeyAttachment *)item; - - if (!sourceAttachment.hockeyAttachmentData) { - BITHockeyLogDebug(@"BITHockeyAttachment instance doesn't contain any data."); - continue; - } - - NSString *filename = [NSString stringWithFormat:@"Attachment_%li.data", (unsigned long)[self.attachments count]]; - if (sourceAttachment.filename) { - filename = sourceAttachment.filename; - } - - BITFeedbackMessageAttachment *attachment = [BITFeedbackMessageAttachment attachmentWithData:sourceAttachment.hockeyAttachmentData contentType:sourceAttachment.contentType]; - attachment.originalFilename = filename; - [self.attachments addObject:attachment]; - } else { - BITHockeyLogWarning(@"WARNING: Unknown item type %@", item); - } - } -} - - -#pragma mark - Keyboard - -- (void)keyboardWasShown:(NSNotification*)aNotification { - NSDictionary* info = [aNotification userInfo]; - CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size; - - BOOL isPortraitOrientation = UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation]); - - CGRect frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height); - if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad) { - frame.size.height -= kbSize.height; - } else { - CGSize windowSize = [[UIScreen mainScreen] bounds].size; - CGFloat windowHeight = windowSize.height - 20; - CGFloat navBarHeight = self.navigationController.navigationBar.frame.size.height; - - if (isPortraitOrientation) { - frame.size.height = windowHeight - navBarHeight - kbSize.height; - } else { - windowHeight = windowSize.height - 20; - CGFloat modalGap = 0.0; - if (windowHeight - kbSize.height < self.view.bounds.size.height) { - modalGap = 30; - } else { - modalGap = (windowHeight - self.view.bounds.size.height) / 2; - } - frame.size.height = windowSize.height - navBarHeight - modalGap - kbSize.height; - } - } - [self.contentViewContainer setFrame:frame]; - - [self performSelector:@selector(refreshAttachmentScrollview) withObject:nil afterDelay:0.0]; -} - -- (void)keyboardWillBeHidden:(NSNotification*) __unused aNotification { - CGRect frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height); - [self.contentViewContainer setFrame:frame]; -} - - -#pragma mark - View lifecycle - -- (void)viewDidLoad { - [super viewDidLoad]; - - self.title = BITHockeyLocalizedString(@"HockeyFeedbackComposeTitle"); - self.view.backgroundColor = [UIColor whiteColor]; - - // Do any additional setup after loading the view. - self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel - target:self - action:@selector(dismissAction:)]; - self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:BITHockeyLocalizedString(@"HockeyFeedbackComposeSend") - style:UIBarButtonItemStyleDone - target:self - action:@selector(sendAction:)]; - - // Container that contains both the textfield and eventually the photo scroll view on the right side - self.contentViewContainer = [[UIView alloc] initWithFrame:self.view.bounds]; - self.contentViewContainer.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; - - [self.view addSubview:self.contentViewContainer]; - - // message input textfield - self.textView = [[UITextView alloc] initWithFrame:self.view.bounds]; - self.textView.font = [UIFont systemFontOfSize:17]; - self.textView.delegate = self; - self.textView.backgroundColor = [UIColor whiteColor]; - self.textView.returnKeyType = UIReturnKeyDefault; - self.textView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; - self.textView.accessibilityHint = BITHockeyLocalizedString(@"HockeyAccessibilityHintRequired"); - - [self.contentViewContainer addSubview:self.textView]; - - // Add Photo Button + Container that's displayed above the keyboard. - if([BITHockeyHelper isPhotoAccessPossible]) { - self.textAccessoryView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.frame), 44)]; - self.textAccessoryView.backgroundColor = [UIColor colorWithRed:(CGFloat)0.9 green:(CGFloat)0.9 blue:(CGFloat)0.9 alpha:(CGFloat)1.0]; - - self.addPhotoButton = [UIButton buttonWithType:UIButtonTypeCustom]; - [self.addPhotoButton setTitle:BITHockeyLocalizedString(@"HockeyFeedbackComposeAttachmentAddImage") forState:UIControlStateNormal]; - [self.addPhotoButton setTitleColor:[UIColor darkGrayColor] forState:UIControlStateNormal]; - [self.addPhotoButton setTitleColor:[UIColor lightGrayColor] forState:UIControlStateDisabled]; - self.addPhotoButton.frame = CGRectMake(0, 0, CGRectGetWidth(self.view.frame), 44); - [self.addPhotoButton addTarget:self action:@selector(addPhotoAction:) forControlEvents:UIControlEventTouchUpInside]; - self.addPhotoButton.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin; - [self.textAccessoryView addSubview:self.addPhotoButton]; - } - - - - if (!self.hideImageAttachmentButton) { - self.textView.inputAccessoryView = self.textAccessoryView; - } - - // This could be a subclass, yet - self.attachmentScrollView = [[UIScrollView alloc] initWithFrame:CGRectZero]; - self.attachmentScrollView.scrollEnabled = YES; - self.attachmentScrollView.bounces = YES; - self.attachmentScrollView.autoresizesSubviews = NO; - self.attachmentScrollView.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleRightMargin; -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0 - if (@available(iOS 11.0, *)) { - self.attachmentScrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentAlways; - } -#endif - [self.contentViewContainer addSubview:self.attachmentScrollView]; -} - -- (void)viewWillAppear:(BOOL)animated { - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(keyboardWasShown:) - name:UIKeyboardDidShowNotification object:nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(keyboardWillBeHidden:) - name:UIKeyboardWillHideNotification object:nil]; - - self.manager.currentFeedbackComposeViewController = self; - - [super viewWillAppear:animated]; - - if (self.text && self.textView.text.length == 0) { - self.textView.text = self.text; - } - - if (self.isStatusBarHiddenBeforeShowingPhotoPicker) { - [self setNeedsStatusBarAppearanceUpdate]; - } - - self.isStatusBarHiddenBeforeShowingPhotoPicker = nil; - - [self updateBarButtonState]; -} - -- (BOOL)prefersStatusBarHidden { - if (self.isStatusBarHiddenBeforeShowingPhotoPicker) { - return self.isStatusBarHiddenBeforeShowingPhotoPicker.boolValue; - } - - return [super prefersStatusBarHidden]; -} - -- (void)viewDidAppear:(BOOL)animated { - [super viewDidAppear:animated]; - BITFeedbackManager *strongManager = self.manager; - if ([strongManager askManualUserDataAvailable] && - ([strongManager requireManualUserDataMissing] || - ![strongManager didAskUserData]) - ) { - if (!self.blockUserDataScreen) - [self setUserDataAction]; - } else { - // Invoke delayed to fix iOS 7 iPad landscape bug, where this view will be moved if not called delayed - [self.textView performSelector:@selector(becomeFirstResponder) withObject:nil afterDelay:0.0]; - [self refreshAttachmentScrollview]; - } -} - -- (void)viewWillDisappear:(BOOL)animated { - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidShowNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil]; - - self.manager.currentFeedbackComposeViewController = nil; - - [super viewWillDisappear:animated]; -} - -- (void)viewDidDisappear:(BOOL)animated { - [super viewDidDisappear:animated]; -} - -- (void)refreshAttachmentScrollview { - CGFloat scrollViewWidth = 0; - - if (self.imageAttachments.count){ - scrollViewWidth = 100; - } - - CGRect textViewFrame = self.textView.frame; - - CGRect scrollViewFrame = self.attachmentScrollView.frame; - - BOOL alreadySetup = CGRectGetWidth(scrollViewFrame) > 0; - - if (alreadySetup && self.imageAttachments.count == 0) { - textViewFrame.size.width += 100; - self.textView.frame = textViewFrame; - scrollViewFrame.size.width = 0; - self.attachmentScrollView.frame = scrollViewFrame; - return; - } - - if (!alreadySetup) { - CGSize tempTextViewSize = CGSizeMake(self.contentViewContainer.frame.size.width, self.contentViewContainer.frame.size.height); - textViewFrame.size = tempTextViewSize; - textViewFrame.size.width -= scrollViewWidth; - // height has to be identical to the textview! - scrollViewFrame = CGRectMake(CGRectGetMaxX(textViewFrame), self.view.frame.origin.y, scrollViewWidth, CGRectGetHeight(self.textView.bounds)); - self.textView.frame = textViewFrame; - self.attachmentScrollView.frame = scrollViewFrame; - self.attachmentScrollView.contentInset = self.textView.contentInset; - } - - if (self.imageAttachments.count > self.attachmentScrollViewImageViews.count){ - NSInteger numberOfViewsToCreate = self.imageAttachments.count - self.attachmentScrollViewImageViews.count; - for (int i = 0; i 0 ) { - self.navigationItem.rightBarButtonItem.enabled = YES; - } else { - self.navigationItem.rightBarButtonItem.enabled = NO; - } - - if(self.addPhotoButton) { - if (self.imageAttachments.count > 2){ - [self.addPhotoButton setEnabled:NO]; - } else { - [self.addPhotoButton setEnabled:YES]; - } - } -} - -- (void)removeAttachmentScrollView { - CGRect frame = self.attachmentScrollView.frame; - frame.size.width = 0; - self.attachmentScrollView.frame = frame; - - frame = self.textView.frame; - frame.size.width += 100; - self.textView.frame = frame; -} - - -#pragma mark - UIViewController Rotation - -- (UIInterfaceOrientationMask)supportedInterfaceOrientations{ - return UIInterfaceOrientationMaskAll; -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation) __unused fromInterfaceOrientation { - [self removeAttachmentScrollView]; - - [self refreshAttachmentScrollview]; -} -#pragma clang diagnostic pop - -#pragma mark - Private methods - -- (void)setUserDataAction { - BITFeedbackUserDataViewController *userController = [[BITFeedbackUserDataViewController alloc] initWithStyle:UITableViewStyleGrouped]; - userController.delegate = self; - - UINavigationController *navController = [self.manager customNavigationControllerWithRootViewController:userController - presentationStyle:UIModalPresentationFormSheet]; - - [self presentViewController:navController animated:YES completion:nil]; -} - -#pragma mark - Actions - -- (void)dismissAction:(id) __unused sender { - for (BITFeedbackMessageAttachment *attachment in self.attachments){ - [attachment deleteContents]; - } - - [self dismissWithResult:BITFeedbackComposeResultCancelled]; -} - -- (void)sendAction:(id) __unused sender { - if ([self.textView isFirstResponder]) - [self.textView resignFirstResponder]; - - NSString *text = self.textView.text; - - [self.manager submitMessageWithText:text andAttachments:self.attachments]; - - [self dismissWithResult:BITFeedbackComposeResultSubmitted]; -} - -- (void)dismissWithResult:(BITFeedbackComposeResult) result { - id strongDelegate = self.delegate; - if([strongDelegate respondsToSelector:@selector(feedbackComposeViewController:didFinishWithResult:)]) { - [strongDelegate feedbackComposeViewController:self didFinishWithResult:result]; - } else { - [self dismissViewControllerAnimated:YES completion:nil]; - } -} - -- (void)addPhotoAction:(id) __unused sender { - if (self.actionSheetVisible) return; - - self.isStatusBarHiddenBeforeShowingPhotoPicker = @([[UIApplication sharedApplication] isStatusBarHidden]); - - // add photo. - UIImagePickerController *pickerController = [[UIImagePickerController alloc] init]; - pickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; - pickerController.delegate = self; - pickerController.editing = NO; - pickerController.navigationBar.barStyle = self.manager.barStyle; - [self presentViewController:pickerController animated:YES completion:nil]; -} - -- (void)scrollViewTapped:(id) __unused unused { - UIMenuController *menuController = [UIMenuController sharedMenuController]; - [menuController setTargetRect:CGRectMake([self.tapRecognizer locationInView:self.view].x, [self.tapRecognizer locationInView:self.view].x, 1, 1) inView:self.view]; - [menuController setMenuVisible:YES animated:YES]; -} - -- (void)paste:(id) __unused sender { - -} - -#pragma mark - UIImagePickerControllerDelegate - -- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { - UIImage *pickedImage = info[UIImagePickerControllerOriginalImage]; - - if (pickedImage){ - NSData *imageData = UIImageJPEGRepresentation(pickedImage, (CGFloat)0.7); - BITFeedbackMessageAttachment *newAttachment = [BITFeedbackMessageAttachment attachmentWithData:imageData contentType:@"image/jpeg"]; - NSURL *imagePath = [info objectForKey:@"UIImagePickerControllerReferenceURL"]; - NSString *imageName = [imagePath lastPathComponent]; - newAttachment.originalFilename = imageName; - [self.attachments addObject:newAttachment]; - [self.imageAttachments addObject:newAttachment]; - } - - [picker dismissViewControllerAnimated:YES completion:nil]; -} - -- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { - [picker dismissViewControllerAnimated:YES completion:nil]; -} - -- (void)imageButtonAction:(UIButton *)sender { - // determine the index of the feedback - NSInteger index = [self.attachmentScrollViewImageViews indexOfObject:sender]; - - self.selectedAttachmentIndex = (self.attachmentScrollViewImageViews.count - index - 1); - /* We won't use this for now until we have a more robust solution for displaying UIAlertController - // requires iOS 8 - id uialertcontrollerClass = NSClassFromString(@"UIAlertController"); - if (uialertcontrollerClass) { - __weak typeof(self) weakSelf = self; - - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil - message:nil - preferredStyle:UIAlertControllerStyleActionSheet]; - - - UIAlertAction *cancelAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"HockeyFeedbackComposeAttachmentCancel") - style:UIAlertActionStyleCancel - handler:^(UIAlertAction * action) { - typeof(self) strongSelf = weakSelf; - [strongSelf cancelAction]; - _actionSheetVisible = NO; - }]; - - [alertController addAction:cancelAction]; - - UIAlertAction *editAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"HockeyFeedbackComposeAttachmentEdit") - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - typeof(self) strongSelf = weakSelf; - [strongSelf editAction]; - _actionSheetVisible = NO; - }]; - - [alertController addAction:editAction]; - - UIAlertAction *deleteAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"HockeyFeedbackComposeAttachmentDelete") - style:UIAlertActionStyleDestructive - handler:^(UIAlertAction * action) { - typeof(self) strongSelf = weakSelf; - [strongSelf deleteAction]; - _actionSheetVisible = NO; - }]; - - [alertController addAction:deleteAction]; - - [self presentViewController:alertController animated:YES completion:nil]; - } else { - */ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle: nil - delegate: self - cancelButtonTitle: BITHockeyLocalizedString(@"HockeyFeedbackComposeAttachmentCancel") - destructiveButtonTitle: BITHockeyLocalizedString(@"HockeyFeedbackComposeAttachmentDelete") - otherButtonTitles: BITHockeyLocalizedString(@"HockeyFeedbackComposeAttachmentEdit"), nil]; - - [actionSheet showFromRect: sender.frame inView: self.attachmentScrollView animated: YES]; -#pragma clang diagnostic push - /*}*/ - - self.actionSheetVisible = YES; - if ((UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) || ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){9,0,0}])) { - [self.textView resignFirstResponder]; - } -} - - -#pragma mark - BITFeedbackUserDataDelegate - -- (void)userDataUpdateCancelled { - self.blockUserDataScreen = YES; - - if ([self.manager requireManualUserDataMissing]) { - if ([self.navigationController respondsToSelector:@selector(dismissViewControllerAnimated:completion:)]) { - [self.navigationController dismissViewControllerAnimated:YES - completion:^(void) { - [self dismissViewControllerAnimated:YES completion:nil]; - }]; - } else { - [self dismissViewControllerAnimated:YES completion:nil]; - [self performSelector:@selector(dismissAction:) withObject:nil afterDelay:0.4]; - } - } else { - [self.navigationController dismissViewControllerAnimated:YES completion:nil]; - } -} - -- (void)userDataUpdateFinished { - [self.manager saveMessages]; - - [self.navigationController dismissViewControllerAnimated:YES completion:nil]; -} - - -#pragma mark - UITextViewDelegate - -- (void)textViewDidChange:(UITextView *) __unused textView { - [self updateBarButtonState]; -} - - -#pragma mark - UIActionSheet Delegate - -- (void)deleteAction { - if (self.selectedAttachmentIndex != NSNotFound){ - UIButton *imageButton = self.attachmentScrollViewImageViews[self.selectedAttachmentIndex]; - BITFeedbackMessageAttachment *attachment = self.imageAttachments[self.selectedAttachmentIndex]; - [attachment deleteContents]; // mandatory call to delete the files associated. - [self.imageAttachments removeObject:attachment]; - [self.attachments removeObject:attachment]; - [imageButton removeFromSuperview]; - [self.attachmentScrollViewImageViews removeObject:imageButton]; - } - self.selectedAttachmentIndex = NSNotFound; - - [self refreshAttachmentScrollview]; - - if ((UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) || ([[NSProcessInfo processInfo] respondsToSelector:@selector(isOperatingSystemAtLeastVersion:)] && [[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){9,0,0}])) { - [self.textView becomeFirstResponder]; - } -} - -- (void)editAction { - if (self.selectedAttachmentIndex != NSNotFound){ - BITFeedbackMessageAttachment *attachment = self.imageAttachments[self.selectedAttachmentIndex]; - BITImageAnnotationViewController *annotationEditor = [[BITImageAnnotationViewController alloc ] init]; - annotationEditor.delegate = self; - UINavigationController *navController = [self.manager customNavigationControllerWithRootViewController:annotationEditor presentationStyle:UIModalPresentationFullScreen]; - annotationEditor.image = attachment.imageRepresentation; - [self presentViewController:navController animated:YES completion:nil]; - } -} - -- (void)cancelAction { - if ((UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) || ([[NSProcessInfo processInfo] respondsToSelector:@selector(isOperatingSystemAtLeastVersion:)] && [[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){9,0,0}])) { - [self.textView becomeFirstResponder]; - } -} - -- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex { - if (buttonIndex == [actionSheet destructiveButtonIndex]) { - [self deleteAction]; - } else if (buttonIndex != [actionSheet cancelButtonIndex]) { - [self editAction]; - } else { - [self cancelAction]; - } - self.actionSheetVisible = NO; -} - - -#pragma mark - Image Annotation Delegate - -- (void)annotationController:(BITImageAnnotationViewController *) __unused annotationController didFinishWithImage:(UIImage *)image { - if (self.selectedAttachmentIndex != NSNotFound){ - BITFeedbackMessageAttachment *attachment = self.imageAttachments[self.selectedAttachmentIndex]; - [attachment replaceData:UIImageJPEGRepresentation(image, (CGFloat)0.7)]; - } - - self.selectedAttachmentIndex = NSNotFound; -} - -- (void)annotationControllerDidCancel:(BITImageAnnotationViewController *) __unused annotationController { - self.selectedAttachmentIndex = NSNotFound; -} - -@end - -#endif /* HOCKEYSDK_FEATURE_FEEDBACK */ diff --git a/submodules/HockeySDK-iOS/Classes/BITFeedbackComposeViewControllerDelegate.h b/submodules/HockeySDK-iOS/Classes/BITFeedbackComposeViewControllerDelegate.h deleted file mode 100644 index 99bac6cf84..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITFeedbackComposeViewControllerDelegate.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import - -#import "HockeySDKNullability.h" -NS_ASSUME_NONNULL_BEGIN - -/** - * The users action when composing a message - */ -typedef NS_ENUM(NSUInteger, BITFeedbackComposeResult) { - /** - * user hit cancel - */ - BITFeedbackComposeResultCancelled, - /** - * user hit submit - */ - BITFeedbackComposeResultSubmitted, -}; - -@class BITFeedbackComposeViewController; - -/** - * The `BITFeedbackComposeViewControllerDelegate` formal protocol defines methods further configuring - * the behaviour of `BITFeedbackComposeViewController`. - */ - -@protocol BITFeedbackComposeViewControllerDelegate - -@optional - -///----------------------------------------------------------------------------- -/// @name View Controller Management -///----------------------------------------------------------------------------- - -/** - * Invoked once the compose screen is finished via send or cancel - * - * If this is implemented, it's the responsibility of this method to dismiss the presented - * `BITFeedbackComposeViewController` - * - * @param composeViewController The `BITFeedbackComposeViewController` instance invoking this delegate - * @param composeResult The user action the lead to closing the compose view - */ -- (void)feedbackComposeViewController:(BITFeedbackComposeViewController *)composeViewController - didFinishWithResult:(BITFeedbackComposeResult) composeResult; -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/HockeySDK-iOS/Classes/BITFeedbackListViewCell.h b/submodules/HockeySDK-iOS/Classes/BITFeedbackListViewCell.h deleted file mode 100644 index f8947a50ec..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITFeedbackListViewCell.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - - -#import -#import "BITFeedbackMessage.h" -#import "BITAttributedLabel.h" - -@class BITFeedbackMessageAttachment; - -@protocol BITFeedbackListViewCellDelegate - -- (void)listCell:(id)cell didSelectAttachment:(BITFeedbackMessageAttachment *)attachment; - -@end - -/** - * Cell background style - */ -typedef NS_ENUM(NSUInteger, BITFeedbackListViewCellBackgroundStyle) { - /** - * For even rows - */ - BITFeedbackListViewCellBackgroundStyleNormal = 0, - /** - * For uneven rows - */ - BITFeedbackListViewCellBackgroundStyleAlternate = 1 -}; - - -@interface BITFeedbackListViewCell : UITableViewCell - -@property (nonatomic, strong) BITFeedbackMessage *message; - -@property (nonatomic) BITFeedbackListViewCellBackgroundStyle backgroundStyle; - -@property (nonatomic, strong) BITAttributedLabel *labelText; - -@property (nonatomic, weak) id delegate; - -+ (CGFloat) heightForRowWithMessage:(BITFeedbackMessage *)message tableViewWidth:(CGFloat)width; - -- (void)setAttachments:(NSArray *)attachments; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITFeedbackListViewCell.m b/submodules/HockeySDK-iOS/Classes/BITFeedbackListViewCell.m deleted file mode 100644 index 59d661ad37..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITFeedbackListViewCell.m +++ /dev/null @@ -1,362 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_FEEDBACK - -#import "HockeySDKPrivate.h" - -#import "BITFeedbackListViewCell.h" -#import "BITFeedbackMessageAttachment.h" -#import "BITActivityIndicatorButton.h" -#import "BITFeedbackManagerPrivate.h" - -#import - -#define BACKGROUNDCOLOR_DEFAULT_OS7 BIT_RGBCOLOR(255, 255, 255) -#define BACKGROUNDCOLOR_ALTERNATE_OS7 BIT_RGBCOLOR(255, 255, 255) - -#define TEXTCOLOR_TITLE BIT_RGBCOLOR(75, 75, 75) - -#define TEXTCOLOR_DEFAULT BIT_RGBCOLOR(25, 25, 25) -#define TEXTCOLOR_PENDING BIT_RGBCOLOR(75, 75, 75) - -#define TITLE_FONTSIZE 12 -#define TEXT_FONTSIZE 15 - -#define FRAME_SIDE_BORDER 10 -#define FRAME_TOP_BORDER 8 -#define FRAME_BOTTOM_BORDER 5 - -#define LABEL_TITLE_Y 3 -#define LABEL_TITLE_HEIGHT 15 - -#define LABEL_TEXT_Y 25 - -#define ATTACHMENT_SIZE 45 - - -@interface BITFeedbackListViewCell () - -@property (nonatomic, strong) NSDateFormatter *dateFormatter; -@property (nonatomic, strong) NSDateFormatter *timeFormatter; - -@property (nonatomic, strong) UILabel *labelTitle; - -@property (nonatomic, strong) NSMutableArray *attachmentViews; - -@property (nonatomic, strong) UIView *accessoryBackgroundView; - -@property (nonatomic, strong) id updateAttachmentNotification; - -@end - - -@implementation BITFeedbackListViewCell - - -- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { - self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; - if (self) { - // Initialization code - _backgroundStyle = BITFeedbackListViewCellBackgroundStyleNormal; - - _message = nil; - - _dateFormatter = [[NSDateFormatter alloc] init]; - [_dateFormatter setTimeStyle:NSDateFormatterNoStyle]; - [_dateFormatter setDateStyle:NSDateFormatterMediumStyle]; - [_dateFormatter setLocale:[NSLocale currentLocale]]; - [_dateFormatter setDoesRelativeDateFormatting:YES]; - - _timeFormatter = [[NSDateFormatter alloc] init]; - [_timeFormatter setTimeStyle:NSDateFormatterShortStyle]; - [_timeFormatter setDateStyle:NSDateFormatterNoStyle]; - [_timeFormatter setLocale:[NSLocale currentLocale]]; - [_timeFormatter setDoesRelativeDateFormatting:YES]; - - _labelTitle = [[UILabel alloc] init]; - _labelTitle.font = [UIFont systemFontOfSize:TITLE_FONTSIZE]; - - _labelText = [[BITAttributedLabel alloc] initWithFrame:CGRectZero]; - _labelText.font = [UIFont systemFontOfSize:TEXT_FONTSIZE]; - _labelText.numberOfLines = 0; - _labelText.textAlignment = NSTextAlignmentLeft; - _labelText.enabledTextCheckingTypes = UIDataDetectorTypeAll; - - _attachmentViews = [NSMutableArray new]; - [self registerObservers]; - } - return self; -} - -- (void)dealloc { - [self unregisterObservers]; -} - - -#pragma mark - Private - -- (void) registerObservers { - __weak typeof(self) weakSelf = self; - if (nil == self.updateAttachmentNotification) { - self.updateAttachmentNotification = [[NSNotificationCenter defaultCenter] addObserverForName:kBITFeedbackUpdateAttachmentThumbnail - object:nil - queue:NSOperationQueue.mainQueue - usingBlock:^(NSNotification *note) { - typeof(self) strongSelf = weakSelf; - [strongSelf updateAttachmentFromNotification:note]; - }]; - } -} - -- (void) unregisterObservers { - if (self.updateAttachmentNotification) { - [[NSNotificationCenter defaultCenter] removeObserver:self.updateAttachmentNotification]; - self.updateAttachmentNotification = nil; - } -} - -- (void) updateAttachmentFromNotification:(NSNotification *)note { - if (!self.message) return; - if (!self.message.attachments) return; - if (self.message.attachments.count == 0) return; - if (!note.object) return; - if (![note.object isKindOfClass:[BITFeedbackMessageAttachment class]]) return; - - BITFeedbackMessageAttachment *attachment = (BITFeedbackMessageAttachment *)note.object; - if (![self.message.attachments containsObject:attachment]) return; - - // The attachment is part of the message used for this cell, so lets update it. - [self setAttachments:self.message.previewableAttachments]; - [self setNeedsLayout]; -} - -- (UIColor *)backgroundColor { - - if (self.backgroundStyle == BITFeedbackListViewCellBackgroundStyleNormal) { - return BACKGROUNDCOLOR_DEFAULT_OS7; - } else { - return BACKGROUNDCOLOR_ALTERNATE_OS7; - } -} - -- (BOOL)isSameDayWithDate1:(NSDate*)date1 date2:(NSDate*)date2 { - NSCalendar* calendar = [NSCalendar currentCalendar]; - - unsigned unitFlags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay; - - NSDateComponents *dateComponent1 = [calendar components:unitFlags fromDate:date1]; - NSDateComponents *dateComponent2 = [calendar components:unitFlags fromDate:date2]; - - return ([dateComponent1 day] == [dateComponent2 day] && - [dateComponent1 month] == [dateComponent2 month] && - [dateComponent1 year] == [dateComponent2 year]); -} - - -#pragma mark - Layout - -+ (CGFloat) heightForRowWithMessage:(BITFeedbackMessage *)message tableViewWidth:(CGFloat)width { - - CGFloat baseHeight = [self heightForTextInRowWithMessage:message tableViewWidth:width]; - - CGFloat attachmentsPerRow = floor(width / (FRAME_SIDE_BORDER + ATTACHMENT_SIZE)); - - CGFloat calculatedHeight = baseHeight + (FRAME_TOP_BORDER + ATTACHMENT_SIZE) * ceil([message previewableAttachments].count / attachmentsPerRow); - - return ceil(calculatedHeight); -} - - - -+ (CGFloat) heightForTextInRowWithMessage:(BITFeedbackMessage *)message tableViewWidth:(CGFloat)width { - CGFloat calculatedHeight; - - if ([message.text respondsToSelector:@selector(boundingRectWithSize:options:attributes:context:)]) { - CGRect calculatedRect = [message.text boundingRectWithSize:CGSizeMake(width - (2 * FRAME_SIDE_BORDER), CGFLOAT_MAX) - options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading - attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:TEXT_FONTSIZE]} - context:nil]; - calculatedHeight = calculatedRect.size.height + FRAME_TOP_BORDER + LABEL_TEXT_Y + FRAME_BOTTOM_BORDER; - - // added to make space for the images. - - - } else { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - calculatedHeight = [message.text sizeWithFont:[UIFont systemFontOfSize:TEXT_FONTSIZE] - constrainedToSize:CGSizeMake(width - (2 * FRAME_SIDE_BORDER), CGFLOAT_MAX) - ].height + FRAME_TOP_BORDER + LABEL_TEXT_Y + FRAME_BOTTOM_BORDER; - -#pragma clang diagnostic pop - } - - return ceil(calculatedHeight); -} - -- (void)setAttachments:(NSArray *)attachments { - for (UIView *view in self.attachmentViews){ - [view removeFromSuperview]; - } - - [self.attachmentViews removeAllObjects]; - - for (BITFeedbackMessageAttachment *attachment in attachments){ - if (attachment.localURL || attachment.sourceURL) { - BITActivityIndicatorButton *imageView = [BITActivityIndicatorButton buttonWithType:UIButtonTypeCustom]; - - if (attachment.localURL){ - [imageView setImage:[attachment thumbnailWithSize:CGSizeMake(ATTACHMENT_SIZE, ATTACHMENT_SIZE)] forState:UIControlStateNormal]; - [imageView setShowsActivityIndicator:NO]; - } else { - [imageView setImage:nil forState:UIControlStateNormal]; - [imageView setShowsActivityIndicator:YES]; - } - [imageView setContentMode:UIViewContentModeScaleAspectFit]; - [imageView addTarget:self action:@selector(imageButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; - - [self.attachmentViews addObject:imageView]; - } - } -} - - -- (void)layoutSubviews { - if (!self.accessoryBackgroundView){ - self.accessoryBackgroundView = [[UIView alloc] initWithFrame:CGRectMake(0, 2, self.frame.size.width * 2, self.frame.size.height - 2)]; - self.accessoryBackgroundView.autoresizingMask = UIViewAutoresizingFlexibleHeight; - self.accessoryBackgroundView.clipsToBounds = YES; - - // colors - self.accessoryBackgroundView.backgroundColor = [self backgroundColor]; - } - - if (self.accessoryBackgroundView.superview){ - [self.accessoryBackgroundView removeFromSuperview]; - } - self.contentView.backgroundColor = [self backgroundColor]; - self.labelTitle.backgroundColor = [self backgroundColor]; - self.labelText.backgroundColor = [self backgroundColor]; - - self.labelTitle.textColor = TEXTCOLOR_TITLE; - if (self.message.status == BITFeedbackMessageStatusSendPending || self.message.status == BITFeedbackMessageStatusSendInProgress) { - [self.labelText setTextColor:TEXTCOLOR_PENDING]; - } else { - [self.labelText setTextColor:TEXTCOLOR_DEFAULT]; - } - - // background for deletion accessory view - - - // header - NSString *dateString = @""; - if (self.message.status == BITFeedbackMessageStatusSendPending || self.message.status == BITFeedbackMessageStatusSendInProgress) { - dateString = BITHockeyLocalizedString(@"Pending"); - } else if (self.message.date) { - if ([self isSameDayWithDate1:[NSDate date] date2:self.message.date]) { - dateString = [self.timeFormatter stringFromDate:self.message.date]; - } else { - dateString = [self.dateFormatter stringFromDate:self.message.date]; - } - } - [self.labelTitle setText:dateString]; - [self.labelTitle setFrame:CGRectMake(FRAME_SIDE_BORDER, FRAME_TOP_BORDER + LABEL_TITLE_Y, self.frame.size.width - (2 * FRAME_SIDE_BORDER), LABEL_TITLE_HEIGHT)]; - - if (self.message.userMessage) { - self.labelTitle.textAlignment = NSTextAlignmentRight; - self.labelText.textAlignment = NSTextAlignmentRight; - } else { - self.labelTitle.textAlignment = NSTextAlignmentLeft; - self.labelText.textAlignment = NSTextAlignmentLeft; - } - - [self addSubview:self.labelTitle]; - - // text - [self.labelText setText:self.message.text]; - CGSize sizeForTextLabel = CGSizeMake(self.frame.size.width - (2 * FRAME_SIDE_BORDER), - [[self class] heightForTextInRowWithMessage:self.message tableViewWidth:self.frame.size.width] - LABEL_TEXT_Y - FRAME_BOTTOM_BORDER); - - [self.labelText setFrame:CGRectMake(FRAME_SIDE_BORDER, LABEL_TEXT_Y, sizeForTextLabel.width, sizeForTextLabel.height)]; - - [self addSubview:self.labelText]; - - CGFloat baseOffsetOfText = CGRectGetMaxY(self.labelText.frame); - - - int i = 0; - - CGFloat attachmentsPerRow = floor(self.frame.size.width / (FRAME_SIDE_BORDER + ATTACHMENT_SIZE)); - - for (BITActivityIndicatorButton *imageButton in self.attachmentViews) { - imageButton.contentMode = UIViewContentModeScaleAspectFit; - imageButton.imageView.contentMode = UIViewContentModeScaleAspectFill; - - if (!self.message.userMessage) { - imageButton.frame = CGRectMake(FRAME_SIDE_BORDER + (FRAME_SIDE_BORDER + ATTACHMENT_SIZE) * (i%(int)attachmentsPerRow) , floor(i/attachmentsPerRow)*(FRAME_SIDE_BORDER + ATTACHMENT_SIZE) + baseOffsetOfText , ATTACHMENT_SIZE, ATTACHMENT_SIZE); - } else { - imageButton.frame = CGRectMake(self.frame.size.width - FRAME_SIDE_BORDER - ATTACHMENT_SIZE - ((FRAME_SIDE_BORDER + ATTACHMENT_SIZE) * (i%(int)attachmentsPerRow) ), floor(i/attachmentsPerRow)*(FRAME_SIDE_BORDER + ATTACHMENT_SIZE) + baseOffsetOfText , ATTACHMENT_SIZE, ATTACHMENT_SIZE); - } - - if (!imageButton.superview) { - if (self.accessoryBackgroundView.superview) { - [self insertSubview:imageButton aboveSubview:self.accessoryBackgroundView]; - } else { - [self addSubview:imageButton]; - } - } - - i++; - } - - [super layoutSubviews]; -} - -- (void)imageButtonPressed:(id)sender { - id strongDelegate = self.delegate; - if ([strongDelegate respondsToSelector:@selector(listCell:didSelectAttachment:)]) { - NSUInteger index = [self.attachmentViews indexOfObject:sender]; - if (index != NSNotFound && [self.message previewableAttachments].count > index) { - BITFeedbackMessageAttachment *attachment = [self.message previewableAttachments][index]; - [strongDelegate listCell:self didSelectAttachment:attachment]; - } - } -} - -- (NSString *)accessibilityLabel { - NSString *messageTime = [self.labelTitle accessibilityLabel]; - NSString *messageText = [self.labelText accessibilityLabel]; - return [NSString stringWithFormat:@"%@, %@", messageTime, messageText]; -} - -@end - -#endif /* HOCKEYSDK_FEATURE_FEEDBACK */ diff --git a/submodules/HockeySDK-iOS/Classes/BITFeedbackListViewController.h b/submodules/HockeySDK-iOS/Classes/BITFeedbackListViewController.h deleted file mode 100644 index 62fd91d518..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITFeedbackListViewController.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - - -#import -#import - -#import "BITHockeyBaseViewController.h" - -/** - View controller providing a default interface to manage feedback - - The message list interface contains options to locally delete single messages - by swiping over them, or deleting all messages. This will not delete the messages - on the server though! - - It is also integrates actions to invoke the user interface to compose a new messages, - reload the list content from the server and changing the users name or email if these - are allowed to be set. - - To add this view controller to your own app and push it onto a navigation stack, - don't create the instance yourself, but use the following code to get a correct instance: - - [[BITHockeyManager sharedHockeyManager].feedbackManager feedbackListViewController:NO] - - To show it modally, use the following code instead: - - [[BITHockeyManager sharedHockeyManager].feedbackManager feedbackListViewController:YES] - - This ensures that the presentation on iOS 6 and iOS 7 will use the current design on each OS Version. - */ - -@interface BITFeedbackListViewController : BITHockeyBaseViewController { -} - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITFeedbackListViewController.m b/submodules/HockeySDK-iOS/Classes/BITFeedbackListViewController.m deleted file mode 100644 index 14a1dcbc98..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITFeedbackListViewController.m +++ /dev/null @@ -1,804 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_FEEDBACK - -#import "HockeySDKPrivate.h" - -#import "BITFeedbackManagerPrivate.h" -#import "BITFeedbackManager.h" -#import "BITFeedbackListViewController.h" -#import "BITFeedbackListViewCell.h" -#import "BITFeedbackComposeViewController.h" -#import "BITFeedbackUserDataViewController.h" -#import "BITFeedbackMessage.h" -#import "BITFeedbackMessageAttachment.h" -#import "BITAttributedLabel.h" - -#import "BITHockeyBaseManagerPrivate.h" - -#import "BITHockeyHelper.h" -#import -#import - -#define DEFAULT_TEXTCOLOR BIT_RGBCOLOR(75, 75, 75) - -#define BORDER_COLOR BIT_RGBCOLOR(215, 215, 215) - - -@interface BITFeedbackListViewController () - -@property (nonatomic, weak) BITFeedbackManager *manager; -@property (nonatomic, strong) NSDateFormatter *lastUpdateDateFormatter; -@property (nonatomic) BOOL userDataComposeFlow; -@property (nonatomic, strong) NSArray *cachedPreviewItems; -@property (nonatomic, strong) NSOperationQueue *thumbnailQueue; -@property (nonatomic) NSInteger deleteButtonSection; -@property (nonatomic) NSInteger userButtonSection; -@property (nonatomic) NSInteger numberOfSectionsBeforeRotation; -@property (nonatomic) NSInteger numberOfMessagesBeforeRotation; - -@end - - -@implementation BITFeedbackListViewController - -- (instancetype)initWithStyle:(UITableViewStyle)style { - if ((self = [super initWithStyle:style])) { - _manager = [BITHockeyManager sharedHockeyManager].feedbackManager; - - _deleteButtonSection = -1; - self.userButtonSection = -1; - _userDataComposeFlow = NO; - - _numberOfSectionsBeforeRotation = -1; - _numberOfMessagesBeforeRotation = -1; - - - _lastUpdateDateFormatter = [[NSDateFormatter alloc] init]; - [_lastUpdateDateFormatter setDateStyle:NSDateFormatterShortStyle]; - [_lastUpdateDateFormatter setTimeStyle:NSDateFormatterShortStyle]; - _lastUpdateDateFormatter.locale = [NSLocale currentLocale]; - - _thumbnailQueue = [NSOperationQueue new]; - } - return self; -} - - -- (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self name:BITHockeyFeedbackMessagesLoadingStarted object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:BITHockeyFeedbackMessagesLoadingFinished object:nil]; - - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(showDelayedUserDataViewController) object:nil]; -} - - -#pragma mark - View lifecycle - -- (void)viewDidLoad { - [super viewDidLoad]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(startLoadingIndicator) - name:BITHockeyFeedbackMessagesLoadingStarted - object:nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(updateList) - name:BITHockeyFeedbackMessagesLoadingFinished - object:nil]; - - self.title = BITHockeyLocalizedString(@"HockeyFeedbackListTitle"); - - self.tableView.delegate = self; - self.tableView.dataSource = self; - self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; - [self.tableView setAutoresizingMask:UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth]; - - if ([UIRefreshControl class]) { - self.refreshControl = [[UIRefreshControl alloc] init]; - [self.refreshControl addTarget:self action:@selector(reloadList) forControlEvents:UIControlEventValueChanged]; - } else { - self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh - target:self - action:@selector(reloadList)]; - } -} - -- (void)startLoadingIndicator { - if ([UIRefreshControl class]) { - [self.refreshControl beginRefreshing]; - } else { - self.navigationItem.rightBarButtonItem.enabled = NO; - } -} - -- (void)stopLoadingIndicator { - if ([UIRefreshControl class]) { - [self.refreshControl endRefreshing]; - } else { - self.navigationItem.rightBarButtonItem.enabled = YES; - } -} - -- (BOOL)isRefreshingWithNewControl { - if ([UIRefreshControl class]) { - return [self.refreshControl isRefreshing]; - } - return NO; -} - -- (void)reloadList { - [self startLoadingIndicator]; - - [self.manager updateMessagesList]; -} - -- (void)updateList { - dispatch_async(dispatch_get_main_queue(), ^{ - CGSize contentSize = self.tableView.contentSize; - CGPoint contentOffset = self.tableView.contentOffset; - - [self refreshPreviewItems]; - [self.tableView reloadData]; - - if (contentSize.height > 0 && - self.tableView.contentSize.height > self.tableView.frame.size.height && - self.tableView.contentSize.height > contentSize.height && - ![self isRefreshingWithNewControl]) - [self.tableView setContentOffset:CGPointMake(contentOffset.x, self.tableView.contentSize.height - contentSize.height + contentOffset.y) animated:NO]; - - [self stopLoadingIndicator]; - - [self.tableView flashScrollIndicators]; - }); -} - -- (void)viewDidAppear:(BOOL)animated { - if (self.userDataComposeFlow) { - self.userDataComposeFlow = NO; - } - BITFeedbackManager *strongManager = self.manager; - strongManager.currentFeedbackListViewController = self; - - [strongManager updateMessagesListIfRequired]; - - if ([strongManager numberOfMessages] == 0 && - [strongManager askManualUserDataAvailable] && - [strongManager requireManualUserDataMissing] && - ![strongManager didAskUserData] - ) { - self.userDataComposeFlow = YES; - - if ([strongManager showFirstRequiredPresentationModal]) { - [self setUserDataAction:nil]; - } else { - // In case of presenting the feedback in a UIPopoverController it appears - // that the animation is not yet finished (though it should) and pushing - // the user data view on top of the navigation stack right away will - // cause the following warning to appear in the console: - // "nested push animation can result in corrupted navigation bar" - [self performSelector:@selector(showDelayedUserDataViewController) withObject:nil afterDelay:0.0]; - } - } else { - [self.tableView reloadData]; - } - - [super viewDidAppear:animated]; -} - -- (void)viewWillDisappear:(BOOL)animated { - self.manager.currentFeedbackListViewController = nil; - - [super viewWillDisappear:animated]; -} - - -#pragma mark - Private methods - -- (void)showDelayedUserDataViewController { - BITFeedbackUserDataViewController *userController = [[BITFeedbackUserDataViewController alloc] initWithStyle:UITableViewStyleGrouped]; - userController.delegate = self; - - [self.navigationController pushViewController:userController animated:YES]; -} - -- (void)setUserDataAction:(id) __unused sender { - BITFeedbackUserDataViewController *userController = [[BITFeedbackUserDataViewController alloc] initWithStyle:UITableViewStyleGrouped]; - userController.delegate = self; - - UINavigationController *navController = [self.manager customNavigationControllerWithRootViewController:userController - presentationStyle:UIModalPresentationFormSheet]; - - [self presentViewController:navController animated:YES completion:nil]; -} - -- (void)newFeedbackAction:(id) __unused sender { - BITFeedbackManager *strongManager = self.manager; - BITFeedbackComposeViewController *composeController = [strongManager feedbackComposeViewController]; - - UINavigationController *navController = [strongManager customNavigationControllerWithRootViewController:composeController - presentationStyle:UIModalPresentationFormSheet]; - - [self presentViewController:navController animated:YES completion:nil]; -} - -- (void)deleteAllMessages { - [self.manager deleteAllMessages]; - [self refreshPreviewItems]; - - [self.tableView reloadData]; -} - -- (void)deleteAllMessagesAction:(id) __unused sender { - NSString *title = BITHockeyLocalizedString(@"HockeyFeedbackListButtonDeleteAllMessages"); - NSString *message = BITHockeyLocalizedString(@"HockeyFeedbackListDeleteAllTitle"); - UIAlertControllerStyle controllerStyle = UIAlertControllerStyleAlert; - if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad) { - controllerStyle = UIAlertControllerStyleActionSheet; - title = BITHockeyLocalizedString(@"HockeyFeedbackListDeleteAllTitle"); - message = nil; - } - __weak typeof(self) weakSelf = self; - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title - message:message - preferredStyle:controllerStyle]; - UIAlertAction* cancelAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"HockeyFeedbackListDeleteAllCancel") - style:UIAlertActionStyleCancel - handler:^(UIAlertAction __unused *action) {}]; - [alertController addAction:cancelAction]; - UIAlertAction* deleteAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"HockeyFeedbackListDeleteAllDelete") - style:UIAlertActionStyleDestructive - handler:^(UIAlertAction __unused *action) { - typeof(self) strongSelf = weakSelf; - [strongSelf deleteAllMessages]; - }]; - [alertController addAction:deleteAction]; - [self presentViewController:alertController animated:YES completion:nil]; -} - -- (UIView*) viewForShowingActionSheetOnPhone { - //find the topmost presented view controller - //and use its view - UIViewController* topMostPresentedViewController = self.view.window.rootViewController; - while(topMostPresentedViewController.presentedViewController) { - topMostPresentedViewController = topMostPresentedViewController.presentedViewController; - } - UIView* view = topMostPresentedViewController.view; - - if(nil == view) { - //hope for the best. Should work - //on simple view(controller) hierarchies - view = self.view; - } - - return view; -} - -#pragma mark - BITFeedbackUserDataDelegate - --(void)userDataUpdateCancelled { - if (self.userDataComposeFlow) { - if ([self.manager showFirstRequiredPresentationModal]) { - __weak typeof(self) weakSelf = self; - [self dismissViewControllerAnimated:YES completion:^(void){ - typeof(self) strongSelf = weakSelf; - [strongSelf.tableView reloadData]; - }]; - } else { - [self.navigationController popToViewController:self animated:YES]; - } - } else { - [self dismissViewControllerAnimated:YES completion:^(void){}]; - } -} - --(void)userDataUpdateFinished { - BITFeedbackManager *strongManager = self.manager; - [strongManager saveMessages]; - [self refreshPreviewItems]; - - if (self.userDataComposeFlow) { - if ([strongManager showFirstRequiredPresentationModal]) { - __weak typeof(self) weakSelf = self; - [self dismissViewControllerAnimated:YES completion:^(void){ - typeof(self) strongSelf = weakSelf; - [strongSelf newFeedbackAction:nil]; - }]; - } else { - BITFeedbackComposeViewController *composeController = [[BITFeedbackComposeViewController alloc] init]; - composeController.delegate = self; - - [self.navigationController pushViewController:composeController animated:YES]; - } - } else { - [self dismissViewControllerAnimated:YES completion:^(void){}]; - } -} - - -#pragma mark - BITFeedbackComposeViewControllerDelegate - -- (void)feedbackComposeViewController:(BITFeedbackComposeViewController *)composeViewController - didFinishWithResult:(BITFeedbackComposeResult)composeResult { - BITFeedbackManager *strongManager = self.manager; - if (self.userDataComposeFlow) { - if ([strongManager showFirstRequiredPresentationModal]) { - __weak typeof(self) weakSelf = self; - [self dismissViewControllerAnimated:YES completion:^(void){ - typeof(self) strongSelf = weakSelf; - [strongSelf.tableView reloadData]; - }]; - } else { - [self.navigationController popToViewController:self animated:YES]; - } - } else { - [self dismissViewControllerAnimated:YES completion:^(void){}]; - } - id strongDelegate = strongManager.delegate; - if ([strongDelegate respondsToSelector:@selector(feedbackComposeViewController:didFinishWithResult:)]) { - [strongDelegate feedbackComposeViewController:composeViewController didFinishWithResult:composeResult]; - } -} - - -#pragma mark - UIViewController Rotation - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { - self.numberOfSectionsBeforeRotation = [self numberOfSectionsInTableView:self.tableView]; - self.numberOfMessagesBeforeRotation = [self.manager numberOfMessages]; - [self.tableView reloadData]; - [self.tableView beginUpdates]; - [self.tableView endUpdates]; - - self.numberOfSectionsBeforeRotation = -1; - self.numberOfMessagesBeforeRotation = -1; - [self.tableView reloadData]; - - [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration]; -} -#pragma clang diagnostic pop - -- (UIInterfaceOrientationMask)supportedInterfaceOrientations{ - return UIInterfaceOrientationMaskAll; -} - -#pragma mark - Table view data source - -- (NSInteger)numberOfSectionsInTableView:(UITableView *) __unused tableView { - if (self.numberOfSectionsBeforeRotation >= 0) - return self.numberOfSectionsBeforeRotation; - - NSInteger sections = 2; - self.deleteButtonSection = -1; - self.userButtonSection = -1; - BITFeedbackManager *strongManager = self.manager; - if ([strongManager isManualUserDataAvailable] || [strongManager didAskUserData]) { - self.userButtonSection = sections; - sections++; - } - - if ([strongManager numberOfMessages] > 0) { - self.deleteButtonSection = sections; - sections++; - } - - return sections; -} - -- (NSInteger)tableView:(UITableView *) __unused tableView numberOfRowsInSection:(NSInteger)section { - if (section == 1) { - if (self.numberOfMessagesBeforeRotation >= 0) - return self.numberOfMessagesBeforeRotation; - return [self.manager numberOfMessages]; - } else { - return 1; - } -} - -- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { - if (section == 0) { - return 30; - } - return [super tableView:tableView heightForHeaderInSection:section]; -} - -- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { - if (section == 0) { - BITFeedbackManager *strongManager = self.manager; - UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 30.0)]; - UILabel *textLabel = [[UILabel alloc] initWithFrame:CGRectMake(16.0, 5.0, self.view.frame.size.width - (CGFloat)32.0, 25.0)]; - textLabel.text = [NSString stringWithFormat:BITHockeyLocalizedString(@"HockeyFeedbackListLastUpdated"), - [strongManager lastCheck] ? [self.lastUpdateDateFormatter stringFromDate:[strongManager lastCheck]] : BITHockeyLocalizedString(@"HockeyFeedbackListNeverUpdated")]; - textLabel.font = [UIFont systemFontOfSize:10]; - textLabel.textColor = DEFAULT_TEXTCOLOR; - [containerView addSubview:textLabel]; - - return containerView; - } - - return [super tableView:tableView viewForHeaderInSection:section]; -} - -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - static NSString *CellIdentifier = @"MessageCell"; - static NSString *LastUpdateIdentifier = @"LastUpdateCell"; - static NSString *ButtonTopIdentifier = @"ButtonTopCell"; - static NSString *ButtonBottomIdentifier = @"ButtonBottomCell"; - static NSString *ButtonDeleteIdentifier = @"ButtonDeleteCell"; - BITFeedbackManager *strongManager = self.manager; - if (indexPath.section == 0 && indexPath.row == 1) { - UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:LastUpdateIdentifier]; - - if (!cell) { - cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:LastUpdateIdentifier]; - cell.textLabel.font = [UIFont systemFontOfSize:10]; - cell.textLabel.textColor = DEFAULT_TEXTCOLOR; - cell.accessoryType = UITableViewCellAccessoryNone; - cell.selectionStyle = UITableViewCellSelectionStyleNone; - cell.textLabel.textAlignment = NSTextAlignmentCenter; - } - cell.textLabel.accessibilityTraits = UIAccessibilityTraitStaticText; - cell.textLabel.text = [NSString stringWithFormat:BITHockeyLocalizedString(@"HockeyFeedbackListLastUpdated"), - [strongManager lastCheck] ? [self.lastUpdateDateFormatter stringFromDate:[strongManager lastCheck]] : BITHockeyLocalizedString(@"HockeyFeedbackListNeverUpdated")]; - - return cell; - } else if (indexPath.section == 0 || indexPath.section >= 2) { - UITableViewCell *cell = nil; - - NSString *identifier = nil; - if (indexPath.section == 0) { - identifier = ButtonTopIdentifier; - } else if (indexPath.section == self.userButtonSection) { - identifier = ButtonBottomIdentifier; - } else { - identifier = ButtonDeleteIdentifier; - } - - cell = [tableView dequeueReusableCellWithIdentifier:identifier]; - - if (!cell) { - cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]; - - cell.textLabel.font = [UIFont systemFontOfSize:14]; - cell.textLabel.numberOfLines = 0; - cell.accessoryType = UITableViewCellAccessoryNone; - cell.selectionStyle = UITableViewCellSelectionStyleGray; - } - - // Set accessibilityTraits to UIAccessibilityTraitNone to make sure we're not setting the trait to an incorrect type for recycled cells. - cell.textLabel.accessibilityTraits = UIAccessibilityTraitNone; - - // button - NSString *titleString = nil; - - UIColor *titleColor = BIT_RGBCOLOR(35, 111, 251); - if ([self.view respondsToSelector:@selector(tintColor)]){ - titleColor = self.view.tintColor; - } - if (indexPath.section == 0) { - cell.textLabel.accessibilityTraits = UIAccessibilityTraitButton; - if ([strongManager numberOfMessages] == 0) { - titleString = BITHockeyLocalizedString(@"HockeyFeedbackListButtonWriteFeedback"); - } else { - titleString = BITHockeyLocalizedString(@"HockeyFeedbackListButtonWriteResponse"); - } - } else if (indexPath.section == self.userButtonSection) { - if ([strongManager requireUserName] == BITFeedbackUserDataElementRequired || - ([strongManager requireUserName] == BITFeedbackUserDataElementOptional && [strongManager userName] != nil) - ) { - cell.textLabel.accessibilityTraits = UIAccessibilityTraitStaticText; - titleString = [NSString stringWithFormat:BITHockeyLocalizedString(@"HockeyFeedbackListButtonUserDataWithName"), [strongManager userName] ?: @"-"]; - } else if ([strongManager requireUserEmail] == BITFeedbackUserDataElementRequired || - ([strongManager requireUserEmail] == BITFeedbackUserDataElementOptional && [strongManager userEmail] != nil) - ) { - cell.textLabel.accessibilityTraits = UIAccessibilityTraitStaticText; - titleString = [NSString stringWithFormat:BITHockeyLocalizedString(@"HockeyFeedbackListButtonUserDataWithEmail"), [strongManager userEmail] ?: @"-"]; - } else if ([strongManager requireUserName] == BITFeedbackUserDataElementOptional) { - cell.textLabel.accessibilityTraits = UIAccessibilityTraitButton; - titleString = BITHockeyLocalizedString(@"HockeyFeedbackListButtonUserDataSetName"); - } else { - cell.textLabel.accessibilityTraits = UIAccessibilityTraitButton; - titleString = BITHockeyLocalizedString(@"HockeyFeedbackListButtonUserDataSetEmail"); - } - } else { - cell.textLabel.accessibilityTraits = UIAccessibilityTraitButton; - titleString = BITHockeyLocalizedString(@"HockeyFeedbackListButtonDeleteAllMessages"); - titleColor = BIT_RGBCOLOR(251, 35, 35); - } - - cell.textLabel.text = titleString; - cell.textLabel.textColor = titleColor; - - return cell; - } else { - BITFeedbackListViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; - - if (!cell) { - cell = [[BITFeedbackListViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; - cell.accessoryType = UITableViewCellAccessoryNone; - cell.selectionStyle = UITableViewCellSelectionStyleNone; - } - - if (indexPath.row == 0 || indexPath.row % 2 == 0) { - cell.backgroundStyle = BITFeedbackListViewCellBackgroundStyleAlternate; - } else { - cell.backgroundStyle = BITFeedbackListViewCellBackgroundStyleNormal; - } - - BITFeedbackMessage *message = [strongManager messageAtIndex:indexPath.row]; - cell.message = message; - cell.labelText.delegate = self; - cell.labelText.userInteractionEnabled = YES; - cell.delegate = self; - [cell setAttachments:message.previewableAttachments]; - - for (BITFeedbackMessageAttachment *attachment in message.attachments){ - if (attachment.needsLoadingFromURL && !attachment.isLoading){ - attachment.isLoading = YES; - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:(NSURL *)[NSURL URLWithString:attachment.sourceURL]]; - __weak typeof (self) weakSelf = self; - NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; - __block NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration]; - - NSURLSessionDataTask *task = [session dataTaskWithRequest:request - completionHandler: ^(NSData *data, NSURLResponse __unused *response, NSError *error) { - typeof (self) strongSelf = weakSelf; - - [session finishTasksAndInvalidate]; - - [strongSelf handleResponseForAttachment:attachment responseData:data error:error]; - }]; - [task resume]; - } - } - - if (indexPath.row != 0) { - UIView *lineView1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, cell.frame.size.width, 1)]; - lineView1.backgroundColor = BORDER_COLOR; - lineView1.autoresizingMask = UIViewAutoresizingFlexibleWidth; - [cell addSubview:lineView1]; - } - - return cell; - } -} - -- (void)handleResponseForAttachment:(BITFeedbackMessageAttachment *)attachment responseData:(NSData *)responseData error:(NSError *) __unused error { - attachment.isLoading = NO; - if (responseData.length) { - dispatch_async(dispatch_get_main_queue(), ^{ - [attachment replaceData:responseData]; - [[NSNotificationCenter defaultCenter] postNotificationName:kBITFeedbackUpdateAttachmentThumbnail object:attachment]; - [[BITHockeyManager sharedHockeyManager].feedbackManager saveMessages]; - [self.tableView reloadData]; - }); - } -} - - -- (BOOL)tableView:(UITableView *) __unused tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { - if (indexPath.section == 1) - return YES; - - return NO; -} - -- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { - if (editingStyle == UITableViewCellEditingStyleDelete) { - BITFeedbackManager *strongManager = self.manager; - BITFeedbackMessage *message = [strongManager messageAtIndex:indexPath.row]; - BOOL messageHasAttachments = ([message attachments].count > 0); - - if ([strongManager deleteMessageAtIndex:indexPath.row]) { - if ([strongManager numberOfMessages] > 0) { - [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; - } else { - [tableView reloadData]; - } - - if (messageHasAttachments) { - [self refreshPreviewItems]; - } - } - } -} - - -#pragma mark - Table view delegate - -- (CGFloat)tableView:(UITableView *) __unused tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { - if (indexPath.section == 0 ) { - return 44; - } - if (indexPath.section >= 2) { - return 44; - } - - BITFeedbackMessage *message = [self.manager messageAtIndex:indexPath.row]; - if (!message) return 44; - - return [BITFeedbackListViewCell heightForRowWithMessage:message tableViewWidth:self.view.frame.size.width]; -} - -- (void)tableView:(UITableView *) __unused tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - if (indexPath.section == 0) { - [self newFeedbackAction:self]; - } else if (indexPath.section == self.userButtonSection) { - [self setUserDataAction:self]; - } else if (indexPath.section == self.deleteButtonSection) { - [self deleteAllMessagesAction:self]; - } -} - -#pragma mark - BITAttributedLabelDelegate - -- (void)attributedLabel:(BITAttributedLabel *) __unused label didSelectLinkWithURL:(NSURL *)url { - UIAlertControllerStyle controllerStyle = UIAlertControllerStyleAlert; - if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad) { - controllerStyle = UIAlertControllerStyleActionSheet; - } - UIAlertController *linkAction = [UIAlertController alertControllerWithTitle:[url absoluteString] - message:nil - preferredStyle:controllerStyle]; - UIAlertAction* cancelAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"HockeyFeedbackListLinkActionCancel") - style:UIAlertActionStyleCancel - handler:^(UIAlertAction __unused *action) {}]; - [linkAction addAction:cancelAction]; - UIAlertAction* openAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"HockeyFeedbackListLinkActionOpen") - style:UIAlertActionStyleDefault - handler:^(UIAlertAction __unused *action) { - [[UIApplication sharedApplication] openURL:(NSURL*)[NSURL URLWithString:(NSString*)[url absoluteString]]]; - }]; - [linkAction addAction:openAction]; - UIAlertAction* copyAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"HockeyFeedbackListLinkActionCopy") - style:UIAlertActionStyleDefault - handler:^(UIAlertAction __unused *action) { - UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; - pasteboard.URL = [NSURL URLWithString:(NSString*)[url absoluteString]]; - }]; - [linkAction addAction:copyAction]; - [self presentViewController:linkAction animated:YES completion:nil]; -} - -#pragma mark - UIActionSheetDelegate - -- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex { - if (buttonIndex == actionSheet.cancelButtonIndex) { - return; - } - - if ([actionSheet tag] == 0) { - if (buttonIndex == [actionSheet destructiveButtonIndex]) { - [self deleteAllMessages]; - } - } else { - if (buttonIndex == [actionSheet firstOtherButtonIndex]) { - [[UIApplication sharedApplication] openURL:(NSURL *)[NSURL URLWithString:actionSheet.title]]; - } else { - UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; - pasteboard.URL = [NSURL URLWithString:actionSheet.title]; - } - } -} - -#pragma mark - ListViewCellDelegate - -- (void)listCell:(id) __unused cell didSelectAttachment:(BITFeedbackMessageAttachment *)attachment { - if (!self.cachedPreviewItems){ - [self refreshPreviewItems]; - } - - QLPreviewController *previewController = [[QLPreviewController alloc] init]; - previewController.dataSource = self; - - [self presentViewController:previewController animated:YES completion:nil]; - - if (self.cachedPreviewItems.count > [self.cachedPreviewItems indexOfObject:attachment]) { - [previewController setCurrentPreviewItemIndex:[self.cachedPreviewItems indexOfObject:attachment]]; - } -} - -- (void)refreshPreviewItems { - self.cachedPreviewItems = nil; - NSMutableArray *collectedAttachments = [NSMutableArray new]; - BITFeedbackManager *strongManager = self.manager; - for (uint i = 0; i < strongManager.numberOfMessages; i++) { - BITFeedbackMessage *message = [strongManager messageAtIndex:i]; - [collectedAttachments addObjectsFromArray:message.previewableAttachments]; - } - - self.cachedPreviewItems = collectedAttachments; -} - -- (NSInteger)numberOfPreviewItemsInPreviewController:(QLPreviewController *) __unused controller { - return self.cachedPreviewItems.count; -} - -- (id )previewController:(QLPreviewController *)controller previewItemAtIndex:(NSInteger)index { - if (index >= 0) { - __weak QLPreviewController* blockController = controller; - BITFeedbackMessageAttachment *attachment = self.cachedPreviewItems[index]; - - if (attachment.needsLoadingFromURL && !attachment.isLoading) { - attachment.isLoading = YES; - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:(NSURL *)[NSURL URLWithString:attachment.sourceURL]]; - - __weak typeof (self) weakSelf = self; - NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; - __block NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration]; - - NSURLSessionDataTask *task = [session dataTaskWithRequest:request - completionHandler: ^(NSData *data, NSURLResponse __unused *response, NSError __unused *error) { - dispatch_async(dispatch_get_main_queue(), ^{ - typeof (self) strongSelf = weakSelf; - - [session finishTasksAndInvalidate]; - - [strongSelf previewController:blockController updateAttachment:attachment data:data]; - }); - }]; - [task resume]; - return attachment; - } else { - return self.cachedPreviewItems[index]; - } - } - - return [self placeholder]; -} - -- (void)previewController:(QLPreviewController *)controller updateAttachment:(BITFeedbackMessageAttachment *)attachment data:( NSData *)data { - attachment.isLoading = NO; - if (data.length) { - [attachment replaceData:data]; - [controller reloadData]; - - [[BITHockeyManager sharedHockeyManager].feedbackManager saveMessages]; - } else { - [controller reloadData]; - } -} - -- (BITFeedbackMessageAttachment *)placeholder { - UIImage *placeholderImage = bit_imageNamed(@"FeedbackPlaceHolder", BITHOCKEYSDK_BUNDLE); - - BITFeedbackMessageAttachment *placeholder = [BITFeedbackMessageAttachment attachmentWithData:UIImageJPEGRepresentation(placeholderImage, (CGFloat)0.7) contentType:@"image/jpeg"]; - - return placeholder; -} - -@end - -#endif /* HOCKEYSDK_FEATURE_FEEDBACK */ diff --git a/submodules/HockeySDK-iOS/Classes/BITFeedbackManager.h b/submodules/HockeySDK-iOS/Classes/BITFeedbackManager.h deleted file mode 100644 index 507788df71..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITFeedbackManager.h +++ /dev/null @@ -1,365 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - - -#import - -#import "BITHockeyBaseManager.h" -#import "BITFeedbackListViewController.h" -#import "BITFeedbackComposeViewController.h" - -#import "HockeySDKNullability.h" -NS_ASSUME_NONNULL_BEGIN - -// Notification message which tells that loading messages finished -#define BITHockeyFeedbackMessagesLoadingStarted @"BITHockeyFeedbackMessagesLoadingStarted" - -// Notification message which tells that loading messages finished -#define BITHockeyFeedbackMessagesLoadingFinished @"BITHockeyFeedbackMessagesLoadingFinished" - - -/** - * Defines behavior of the user data field - */ -typedef NS_ENUM(NSInteger, BITFeedbackUserDataElement) { - /** - * don't ask for this user data element at all - */ - BITFeedbackUserDataElementDontShow = 0, - /** - * the user may provide it, but does not have to - */ - BITFeedbackUserDataElementOptional = 1, - /** - * the user has to provide this to continue - */ - BITFeedbackUserDataElementRequired = 2 -}; - -/** - * Available modes for opening the feedback compose interface with a screenshot attached - */ -typedef NS_ENUM(NSInteger, BITFeedbackObservationMode) { - /** - * No SDK provided trigger is active. - */ - BITFeedbackObservationNone = 0, - /** - * Triggers when the user takes a screenshot. This will grab the latest image from the camera roll. Requires iOS 7 or later! - */ - BITFeedbackObservationModeOnScreenshot = 1, - /** - * Triggers when the user taps with three fingers on the screen. Captures a screenshot and attaches it to the composer. - */ - BITFeedbackObservationModeThreeFingerTap = 2, - /** - * Allows both BITFeedbackObservationModeOnScreenshot and BITFeedbackObservationModeThreeFingerTap at the same time. - */ - BITFeedbackObservationModeAll = 3 -}; - - -@class BITFeedbackMessage; -@protocol BITFeedbackManagerDelegate; - -/** - The feedback module. - - This is the HockeySDK module for letting your users communicate directly with you via - the app and an integrated user interface. It provides a single threaded - discussion with a user running your app. - - You should never create your own instance of `BITFeedbackManager` but use the one provided - by the `[BITHockeyManager sharedHockeyManager]`: - - [BITHockeyManager sharedHockeyManager].feedbackManager - - The user interface provides a list view that can be presented modally using - `[BITFeedbackManager showFeedbackListView]` or adding - `[BITFeedbackManager feedbackListViewController:]` to push onto a navigation stack. - This list integrates all features to load new messages, write new messages, view messages - and ask the user for additional (optional) data like name and email. - - If the user provides the email address, all responses from the server will also be sent - to the user via email and the user is also able to respond directly via email, too. - - The message list interface also contains options to locally delete single messages - by swiping over them, or deleting all messages. This will not delete the messages - on the server, though! - - It also integrates actions to invoke the user interface to compose a new message, - reload the list content from the server and change the users name or email if these - are allowed to be set. - - It is also possible to invoke the user interface to compose a new message in your - own code, by calling `[BITFeedbackManager showFeedbackComposeView]` modally or adding - `[BITFeedbackManager feedbackComposeViewController]` to push onto a navigation stack. - - If new messages are written while the device is offline, the SDK automatically retries to - send them once the app starts again or gets active again, or if the notification - `BITHockeyNetworkDidBecomeReachableNotification` is fired. - - A third option is to include the `BITFeedbackActivity` into an UIActivityViewController. - This can be useful if you present some data that users can not only share but also - report back to the developer because they have some problems, e.g. webcams not working - any more. The activity provides a default title and image that can also be customized. - - New messages are automatically loaded on startup, when the app becomes active again - or when the notification `BITHockeyNetworkDidBecomeReachableNotification` is fired. This - only happens if the user ever did initiate a conversation by writing the first - feedback message. The app developer has to fire this notification to trigger another retry - when it detects the device having network access again. The SDK only retries automatically - when the app becomes active again. - - Implementing the `BITFeedbackManagerDelegate` protocol will notify your app when a new - message was received from the server. The `BITFeedbackComposeViewControllerDelegate` - protocol informs your app about events related to sending feedback messages. - - */ - -@interface BITFeedbackManager : BITHockeyBaseManager - -///----------------------------------------------------------------------------- -/// @name General settings -///----------------------------------------------------------------------------- - - -/** - Define if a name has to be provided by the user when providing feedback - - - `BITFeedbackUserDataElementDontShow`: Don't ask for this user data element at all - - `BITFeedbackUserDataElementOptional`: The user may provide it, but does not have to - - `BITFeedbackUserDataElementRequired`: The user has to provide this to continue - - The default value is `BITFeedbackUserDataElementOptional`. - - @warning If you provide a non nil value for the `BITFeedbackManager` class via - `[BITHockeyManagerDelegate userNameForHockeyManager:componentManager:]` then this - property will automatically be set to `BITFeedbackUserDataElementDontShow` - - @see BITFeedbackUserDataElement - @see requireUserEmail - @see `[BITHockeyManagerDelegate userNameForHockeyManager:componentManager:]` - */ -@property (nonatomic, readwrite) BITFeedbackUserDataElement requireUserName; - - -/** - Define if an email address has to be provided by the user when providing feedback - - If the user provides the email address, all responses from the server will also be send - to the user via email and the user is also able to respond directly via email too. - - - `BITFeedbackUserDataElementDontShow`: Don't ask for this user data element at all - - `BITFeedbackUserDataElementOptional`: The user may provide it, but does not have to - - `BITFeedbackUserDataElementRequired`: The user has to provide this to continue - - The default value is `BITFeedbackUserDataElementOptional`. - - @warning If you provide a non nil value for the `BITFeedbackManager` class via - `[BITHockeyManagerDelegate userEmailForHockeyManager:componentManager:]` then this - property will automatically be set to `BITFeedbackUserDataElementDontShow` - - @see BITFeedbackUserDataElement - @see requireUserName - @see `[BITHockeyManagerDelegate userEmailForHockeyManager:componentManager:]` - */ -@property (nonatomic, readwrite) BITFeedbackUserDataElement requireUserEmail; - - -/** - Indicates if an alert should be shown when new messages have arrived - - This lets the user view the new feedback by choosing the appropriate option - in the alert sheet, and the `BITFeedbackListViewController` will be shown. - - The alert is only shown, if the newest message didn't originate from the current user. - This requires the users email address to be present! The optional userid property - cannot be used, because users could also answer via email and then this information - is not available. - - Default is `YES` - @see feedbackListViewController: - @see requireUserEmail - @see `[BITHockeyManagerDelegate userEmailForHockeyManager:componentManager:]` - */ -@property (nonatomic, readwrite) BOOL showAlertOnIncomingMessages; - - -/** - Define the trigger that opens the feedback composer and attaches a screenshot - - The following modes are available: - - - `BITFeedbackObservationNone`: No SDK based trigger is active. You can implement your - own trigger and then call `[[BITHockeyManager sharedHockeyManager].feedbackManager showFeedbackComposeViewWithGeneratedScreenshot];` to handle your custom events - that should trigger this. - - `BITFeedbackObservationModeOnScreenshot`: Triggers when the user takes a screenshot. - This will grab the latest image from the camera roll. It also requires to add a NSPhotoLibraryUsageDescription to your app's Info.plist. - - `BITFeedbackObservationModeThreeFingerTap`: Triggers when the user taps on the screen with three fingers. Takes a screenshot and attaches it to the composer. It also requires to add a NSPhotoLibraryUsageDescription to your app's Info.plist. - - Default is `BITFeedbackObservationNone`. - If BITFeedbackManger was disabled, setting a new value will be ignored. - @see `[BITHockeyManager disableFeedbackManager]` - - @see showFeedbackComposeViewWithGeneratedScreenshot - */ -@property (nonatomic, readwrite) BITFeedbackObservationMode feedbackObservationMode; - -/** - Don't show the option to add images from the photo library - - This is helpful if your application is landscape only, since the system UI for - selecting an image from the photo library is portrait only - - This setting is used for all feedback compose views that are created by the - `BITFeedbackManager`. If you invoke your own `BITFeedbackComposeViewController`, - then set the appropriate property on the view controller directl!. - */ -@property (nonatomic) BOOL feedbackComposeHideImageAttachmentButton; - - -///----------------------------------------------------------------------------- -/// @name User Interface -///----------------------------------------------------------------------------- - - -/** - Indicates if a forced user data UI presentation is shown modal - - If `requireUserName` and/or `requireUserEmail` are enabled, the first presentation - of `feedbackListViewController:` and subsequent `feedbackComposeViewController:` - will automatically present a UI that lets the user provide this data and compose - a message. By default this is shown (since SDK 3.1) as a modal sheet. - - If you want the SDK to push this UI onto the navigation stack in this specific scenario, - then change the property to `NO`. - - @warning If you are presenting the `BITFeedbackListViewController` in a popover, this property should not be changed! - - Default is `YES` - @see requireUserName - @see requireUserEmail - @see showFeedbackComposeView - @see feedbackComposeViewController - @see showFeedbackListView - @see feedbackListViewController: - */ -@property (nonatomic, readwrite) BOOL showFirstRequiredPresentationModal; - - -/** - Return a screenshot UIImage instance from the current visible screen - - @return UIImage instance containing a screenshot of the current screen - */ -- (UIImage *)screenshot; - - -/** - Present the modal feedback list user interface. - - @warning This methods needs to be called on the main thread! - */ -- (void)showFeedbackListView; - - -/** - Create an feedback list view - - @param modal Return a view ready for modal presentation with integrated navigation bar - @return `BITFeedbackListViewController` The feedback list view controller, - e.g. to push it onto a navigation stack. - */ -- (BITFeedbackListViewController *)feedbackListViewController:(BOOL)modal; - - -/** - Present the modal feedback compose message user interface. - - @warning This methods needs to be called on the main thread! - */ -- (void)showFeedbackComposeView; - -/** - Present the modal feedback compose message user interface with the items given. - - All NSString-Content in the array will be concatenated and result in the message, - while all UIImage and NSData-instances will be turned into attachments. - - Alternatively you can implement the `preparedItemsForFeedbackManager:` delegate method - and call `showFeedbackComposeView` instead. If you use both, the items from the delegate method - and the items passed with this method will be combined. - - @param items an NSArray with objects that should be attached - @see `[BITFeedbackComposeViewController prepareWithItems:]` - @see `BITFeedbackManagerDelegate preparedItemsForFeedbackManager:` - @warning This methods needs to be called on the main thread! - */ -- (void)showFeedbackComposeViewWithPreparedItems:(nullable NSArray *)items; - -/** - Presents a modal feedback compose interface with a screenshot attached which is taken at the time of calling this method. - - This should be used when your own trigger fires. The following code should be used: - - [[BITHockeyManager sharedHockeyManager].feedbackManager showFeedbackComposeViewWithGeneratedScreenshot]; - - @see feedbackObservationMode - @warning This methods needs to be called on the main thread! - */ -- (void)showFeedbackComposeViewWithGeneratedScreenshot; - - -/** - Create a feedback compose view - - This method also adds items from `feedbackComposerPreparedItems` and - the `preparedItemsForFeedbackManager:` delegate methods to the instance of - `BITFeedbackComposeViewController` that will be returned. - - Example to show a modal feedback compose UI with prefilled text - - BITFeedbackComposeViewController *feedbackCompose = [[BITHockeyManager sharedHockeyManager].feedbackManager feedbackComposeViewController]; - - [feedbackCompose prepareWithItems: - @[@"Adding some example default text and also adding a link.", - [NSURL URLWithString:@"http://hockeayyp.net/"]]]; - - UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:feedbackCompose]; - navController.modalPresentationStyle = UIModalPresentationFormSheet; - [self presentViewController:navController animated:YES completion:nil]; - - @return `BITFeedbackComposeViewController` The compose feedback view controller, - e.g. to push it onto a navigation stack. - */ -- (BITFeedbackComposeViewController *)feedbackComposeViewController; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/HockeySDK-iOS/Classes/BITFeedbackManager.m b/submodules/HockeySDK-iOS/Classes/BITFeedbackManager.m deleted file mode 100644 index 04f7d16bd6..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITFeedbackManager.m +++ /dev/null @@ -1,1302 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2016 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_FEEDBACK - -#import - -#import "HockeySDKPrivate.h" - -#import "BITFeedbackManager.h" -#import "BITFeedbackMessageAttachment.h" -#import "BITFeedbackManagerPrivate.h" -#import "BITHockeyBaseManagerPrivate.h" - -#import "HockeySDKNullability.h" -#import "BITHockeyHelper.h" -#import "BITHockeyHelper+Application.h" -#import "BITHockeyAppClient.h" - -#define kBITFeedbackUserDataAsked @"HockeyFeedbackUserDataAsked" -#define kBITFeedbackDateOfLastCheck @"HockeyFeedbackDateOfLastCheck" -#define kBITFeedbackMessages @"HockeyFeedbackMessages" -#define kBITFeedbackToken @"HockeyFeedbackToken" -#define kBITFeedbackUserID @"HockeyFeedbackuserID" -#define kBITFeedbackName @"HockeyFeedbackName" -#define kBITFeedbackEmail @"HockeyFeedbackEmail" -#define kBITFeedbackLastMessageID @"HockeyFeedbackLastMessageID" -#define kBITFeedbackAppID @"HockeyFeedbackAppID" - -NSString *const kBITFeedbackUpdateAttachmentThumbnail = @"BITFeedbackUpdateAttachmentThumbnail"; - -typedef void (^BITLatestImageFetchCompletionBlock)(UIImage *_Nonnull latestImage); - -@interface BITFeedbackManager () - -@property (nonatomic, strong) NSFileManager *fileManager; -@property (nonatomic, copy) NSString *settingsFile; -@property (nonatomic, weak) id appDidBecomeActiveObserver; -@property (nonatomic, weak) id appDidEnterBackgroundObserver; -@property (nonatomic, weak) id networkDidBecomeReachableObserver; -@property (nonatomic) BOOL incomingMessagesAlertShowing; -@property (nonatomic) BOOL didEnterBackgroundState; -@property (nonatomic) BOOL networkRequestInProgress; -@property (nonatomic) BITFeedbackObservationMode observationMode; - -@end - -@implementation BITFeedbackManager - -#pragma mark - Initialization - -- (instancetype)init { - if ((self = [super init])) { - _currentFeedbackListViewController = nil; - _currentFeedbackComposeViewController = nil; - _didAskUserData = NO; - - _requireUserName = BITFeedbackUserDataElementOptional; - _requireUserEmail = BITFeedbackUserDataElementOptional; - _showAlertOnIncomingMessages = YES; - _showFirstRequiredPresentationModal = YES; - - _disableFeedbackManager = NO; - _networkRequestInProgress = NO; - _incomingMessagesAlertShowing = NO; - _lastCheck = nil; - _token = nil; - _lastMessageID = nil; - - self.feedbackList = [NSMutableArray array]; - - _fileManager = [[NSFileManager alloc] init]; - - _settingsFile = [bit_settingsDir() stringByAppendingPathComponent:BITHOCKEY_FEEDBACK_SETTINGS]; - - _userID = nil; - _userName = nil; - _userEmail = nil; - } - return self; -} - -- (void)dealloc { - [self unregisterObservers]; -} - -- (void)didBecomeActiveActions { - if ([self isFeedbackManagerDisabled]) return; - if (!self.didEnterBackgroundState) return; - - self.didEnterBackgroundState = NO; - - if ([self.feedbackList count] == 0) { - [self loadMessages]; - } else { - [self updateAppDefinedUserData]; - } - - if ([self allowFetchingNewMessages]) { - [self updateMessagesList]; - } -} - -- (void)didEnterBackgroundActions { - self.didEnterBackgroundState = NO; - - if ([BITHockeyHelper applicationState] == BITApplicationStateBackground) { - self.didEnterBackgroundState = YES; - } -} - -#pragma mark - Observers - -- (void)registerObservers { - __weak typeof(self) weakSelf = self; - if (nil == self.appDidEnterBackgroundObserver) { - self.appDidEnterBackgroundObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification - object:nil - queue:NSOperationQueue.mainQueue - usingBlock:^(NSNotification __unused *note) { - typeof(self) strongSelf = weakSelf; - [strongSelf didEnterBackgroundActions]; - }]; - } - if (nil == self.appDidBecomeActiveObserver) { - self.appDidBecomeActiveObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification - object:nil - queue:NSOperationQueue.mainQueue - usingBlock:^(NSNotification __unused *note) { - typeof(self) strongSelf = weakSelf; - [strongSelf didBecomeActiveActions]; - }]; - } - if (nil == self.networkDidBecomeReachableObserver) { - self.networkDidBecomeReachableObserver = [[NSNotificationCenter defaultCenter] addObserverForName:BITHockeyNetworkDidBecomeReachableNotification - object:nil - queue:NSOperationQueue.mainQueue - usingBlock:^(NSNotification __unused *note) { - typeof(self) strongSelf = weakSelf; - [strongSelf didBecomeActiveActions]; - }]; - } -} - -- (void)unregisterObservers { - id strongDidEnterBackgroundObserver = self.appDidEnterBackgroundObserver; - id strongDidBecomeActiveObserver = self.appDidBecomeActiveObserver; - id strongNetworkDidBecomeReachableObserver = self.networkDidBecomeReachableObserver; - if (strongDidEnterBackgroundObserver) { - [[NSNotificationCenter defaultCenter] removeObserver:strongDidEnterBackgroundObserver]; - self.appDidEnterBackgroundObserver = nil; - } - if (strongDidBecomeActiveObserver) { - [[NSNotificationCenter defaultCenter] removeObserver:strongDidBecomeActiveObserver]; - self.appDidBecomeActiveObserver = nil; - } - if (strongNetworkDidBecomeReachableObserver) { - [[NSNotificationCenter defaultCenter] removeObserver:strongNetworkDidBecomeReachableObserver]; - self.networkDidBecomeReachableObserver = nil; - } -} - -#pragma mark - Private methods - -- (NSString *)uuidString { - CFUUIDRef theToken = CFUUIDCreate(NULL); - NSString *stringUUID = (__bridge_transfer NSString *) CFUUIDCreateString(NULL, theToken); - CFRelease(theToken); - - return stringUUID; -} - -- (NSString *)uuidAsLowerCaseAndShortened { - return [[[self uuidString] lowercaseString] stringByReplacingOccurrencesOfString:@"-" withString:@""]; -} - -#pragma mark - Feedback Modal UI - -- (UIImage *)screenshot { - return bit_screenshot(); -} - -- (BITFeedbackListViewController *)feedbackListViewController:(BOOL)modal { - return [[BITFeedbackListViewController alloc] initWithStyle:UITableViewStyleGrouped modal:modal]; -} - -- (void)showFeedbackListView { - if (self.currentFeedbackListViewController) { - BITHockeyLogDebug(@"INFO: update view already visible, aborting"); - return; - } - dispatch_async(dispatch_get_main_queue(), ^{ - [self showView:[self feedbackListViewController:YES]]; - }); -} - - -- (BITFeedbackComposeViewController *)feedbackComposeViewController { - BITFeedbackComposeViewController *composeViewController = [[BITFeedbackComposeViewController alloc] init]; - - NSArray *preparedItems = [NSArray array]; - id strongDelegate = self.delegate; - if ([strongDelegate respondsToSelector:@selector(preparedItemsForFeedbackManager:)]) { - preparedItems = [preparedItems arrayByAddingObjectsFromArray:(NSArray *)[strongDelegate preparedItemsForFeedbackManager:self]]; - } - - [composeViewController prepareWithItems:preparedItems]; - [composeViewController setHideImageAttachmentButton:self.feedbackComposeHideImageAttachmentButton]; - - // by default set the delegate to be identical to the one of BITFeedbackManager - [composeViewController setDelegate:strongDelegate]; - return composeViewController; -} - -- (void)showFeedbackComposeView { - [self showFeedbackComposeViewWithPreparedItems:nil]; -} - -- (void)showFeedbackComposeViewWithPreparedItems:(NSArray *)items { - if (self.currentFeedbackComposeViewController) { - BITHockeyLogDebug(@"INFO: Feedback view already visible, aborting"); - return; - } - BITFeedbackComposeViewController *composeView = [self feedbackComposeViewController]; - [composeView prepareWithItems:items]; - dispatch_async(dispatch_get_main_queue(), ^{ - [self showView:composeView]; - }); -} - -- (void)showFeedbackComposeViewWithGeneratedScreenshot { - UIImage *screenshot = bit_screenshot(); - [self showFeedbackComposeViewWithPreparedItems:@[screenshot]]; -} - -#pragma mark - Manager Control - -- (void)startManager { - if ([self isFeedbackManagerDisabled]) return; - - [self registerObservers]; - - [self isiOS10PhotoPolicySet]; - - // we are already delayed, so the notification already came in and this won't invoked twice - switch ([BITHockeyHelper applicationState]) { - case BITApplicationStateActive: - // we did startup, so yes we are coming from background - self.didEnterBackgroundState = YES; - - [self didBecomeActiveActions]; - break; - case BITApplicationStateBackground: - case BITApplicationStateInactive: - case BITApplicationStateUnknown: - // do nothing, wait for active state - break; - } -} - -- (BOOL)allowFetchingNewMessages { - BOOL fetchNewMessages = YES; - id strongDelegate = [BITHockeyManager sharedHockeyManager].delegate; - if ([strongDelegate respondsToSelector:@selector(allowAutomaticFetchingForNewFeedbackForManager:)]) { - fetchNewMessages = [strongDelegate allowAutomaticFetchingForNewFeedbackForManager:self]; - } - return fetchNewMessages; -} - -- (void)updateMessagesList { - if (self.networkRequestInProgress) return; - - NSArray *pendingMessages = [self messagesWithStatus:BITFeedbackMessageStatusSendPending]; - if ([pendingMessages count] > 0) { - [self submitPendingMessages]; - } else { - [self fetchMessageUpdates]; - } -} - -- (void)updateMessagesListIfRequired { - double now = [[NSDate date] timeIntervalSince1970]; - if ((now - [self.lastCheck timeIntervalSince1970] > 30)) { - [self updateMessagesList]; - } -} - -- (BOOL)updateUserIDUsingKeychainAndDelegate { - BOOL availableViaDelegate = NO; - - NSString *userID = [self stringValueFromKeychainForKey:kBITHockeyMetaUserID]; - id strongDelegate = [BITHockeyManager sharedHockeyManager].delegate; - if ([strongDelegate respondsToSelector:@selector(userIDForHockeyManager:componentManager:)]) { - userID = [strongDelegate userIDForHockeyManager:[BITHockeyManager sharedHockeyManager] componentManager:self]; - } - - if (userID) { - availableViaDelegate = YES; - self.userID = userID; - } - - return availableViaDelegate; -} - -- (BOOL)updateUserNameUsingKeychainAndDelegate { - BOOL availableViaDelegate = NO; - - NSString *userName = [self stringValueFromKeychainForKey:kBITHockeyMetaUserName]; - id strongDelegate = [BITHockeyManager sharedHockeyManager].delegate; - if ([strongDelegate respondsToSelector:@selector(userNameForHockeyManager:componentManager:)]) { - userName = [strongDelegate userNameForHockeyManager:[BITHockeyManager sharedHockeyManager] componentManager:self]; - } - - if (userName) { - availableViaDelegate = YES; - self.userName = userName; - self.requireUserName = BITFeedbackUserDataElementDontShow; - } - - return availableViaDelegate; -} - -- (BOOL)updateUserEmailUsingKeychainAndDelegate { - BOOL availableViaDelegate = NO; - - NSString *userEmail = [self stringValueFromKeychainForKey:kBITHockeyMetaUserEmail]; - id strongDelegate = [BITHockeyManager sharedHockeyManager].delegate; - if ([strongDelegate respondsToSelector:@selector(userEmailForHockeyManager:componentManager:)]) { - userEmail = [strongDelegate userEmailForHockeyManager:[BITHockeyManager sharedHockeyManager] componentManager:self]; - } - - if (userEmail) { - availableViaDelegate = YES; - self.userEmail = userEmail; - self.requireUserEmail = BITFeedbackUserDataElementDontShow; - } - - return availableViaDelegate; -} - -- (void)updateAppDefinedUserData { - [self updateUserIDUsingKeychainAndDelegate]; - [self updateUserNameUsingKeychainAndDelegate]; - [self updateUserEmailUsingKeychainAndDelegate]; - - // if both values are shown via the delegates, we never ever did ask and will never ever ask for user data - if (self.requireUserName == BITFeedbackUserDataElementDontShow && - self.requireUserEmail == BITFeedbackUserDataElementDontShow) { - self.didAskUserData = NO; - } -} - -- (BOOL)isiOS10PhotoPolicySet { - BOOL isiOS10PhotoPolicySet = [BITHockeyHelper isPhotoAccessPossible]; - if (bit_isDebuggerAttached()) { - if (!isiOS10PhotoPolicySet) { - BITHockeyLogWarning(@"You are using HockeyApp's Feedback feature in iOS 10 or later. iOS 10 requires you to add the usage strings to your app's info.plist. " - @"Attaching screenshots to feedback is disabled. Please add the String for NSPhotoLibraryUsageDescription to your info.plist to enable screenshot attachments."); - } - } - return isiOS10PhotoPolicySet; -} - -#pragma mark - Local Storage - -- (void)loadMessages { - BOOL userIDViaDelegate = [self updateUserIDUsingKeychainAndDelegate]; - BOOL userNameViaDelegate = [self updateUserNameUsingKeychainAndDelegate]; - BOOL userEmailViaDelegate = [self updateUserEmailUsingKeychainAndDelegate]; - - if (![self.fileManager fileExistsAtPath:self.settingsFile]) - return; - - NSData *codedData = [[NSData alloc] initWithContentsOfFile:self.settingsFile]; - if (codedData == nil) return; - - NSKeyedUnarchiver *unarchiver = nil; - - @try { - unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:codedData]; - } - @catch (NSException __unused *exception) { - return; - } - - if (!userIDViaDelegate) { - if ([unarchiver containsValueForKey:kBITFeedbackUserID]) { - self.userID = [unarchiver decodeObjectForKey:kBITFeedbackUserID]; - [self addStringValueToKeychain:self.userID forKey:kBITFeedbackUserID]; - } - self.userID = [self stringValueFromKeychainForKey:kBITFeedbackUserID]; - } - - if (!userNameViaDelegate) { - if ([unarchiver containsValueForKey:kBITFeedbackName]) { - self.userName = [unarchiver decodeObjectForKey:kBITFeedbackName]; - [self addStringValueToKeychain:self.userName forKey:kBITFeedbackName]; - } - self.userName = [self stringValueFromKeychainForKey:kBITFeedbackName]; - } - - if (!userEmailViaDelegate) { - if ([unarchiver containsValueForKey:kBITFeedbackEmail]) { - self.userEmail = [unarchiver decodeObjectForKey:kBITFeedbackEmail]; - [self addStringValueToKeychain:self.userEmail forKey:kBITFeedbackEmail]; - } - self.userEmail = [self stringValueFromKeychainForKey:kBITFeedbackEmail]; - } - - if ([unarchiver containsValueForKey:kBITFeedbackUserDataAsked]) - self.didAskUserData = YES; - - if ([unarchiver containsValueForKey:kBITFeedbackToken]) { - self.token = [unarchiver decodeObjectForKey:kBITFeedbackToken]; - [self addStringValueToKeychain:self.token forKey:kBITFeedbackToken]; - } - self.token = [self stringValueFromKeychainForKey:kBITFeedbackToken]; - - if ([unarchiver containsValueForKey:kBITFeedbackAppID]) { - NSString *appID = [unarchiver decodeObjectForKey:kBITFeedbackAppID]; - - // the stored thread is from another application identifier, so clear the token - // which will cause the new posts to create a new thread on the server for the - // current app identifier - if ([appID compare:self.appIdentifier] != NSOrderedSame) { - self.token = nil; - } - } - - if ([self shouldForceNewThread]) { - self.token = nil; - } - - if ([unarchiver containsValueForKey:kBITFeedbackDateOfLastCheck]) - self.lastCheck = [unarchiver decodeObjectForKey:kBITFeedbackDateOfLastCheck]; - - if ([unarchiver containsValueForKey:kBITFeedbackLastMessageID]) - self.lastMessageID = [unarchiver decodeObjectForKey:kBITFeedbackLastMessageID]; - - if ([unarchiver containsValueForKey:kBITFeedbackMessages]) { - [self.feedbackList setArray:(NSArray *)[unarchiver decodeObjectForKey:kBITFeedbackMessages]]; - - [self sortFeedbackList]; - - // inform the UI to update its data in case the list is already showing - [[NSNotificationCenter defaultCenter] postNotificationName:BITHockeyFeedbackMessagesLoadingFinished object:nil]; - } - - [unarchiver finishDecoding]; - - if (!self.lastCheck) { - self.lastCheck = [NSDate distantPast]; - } -} - - -- (void)saveMessages { - [self sortFeedbackList]; - - NSMutableData *data = [[NSMutableData alloc] init]; - NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; - - if (self.didAskUserData) - [archiver encodeObject:[NSNumber numberWithBool:YES] forKey:kBITFeedbackUserDataAsked]; - - if (self.token) - [self addStringValueToKeychain:self.token forKey:kBITFeedbackToken]; - - if (self.appIdentifier) - [archiver encodeObject:self.appIdentifier forKey:kBITFeedbackAppID]; - - if (self.userID) - [self addStringValueToKeychain:self.userID forKey:kBITFeedbackUserID]; - - if (self.userName) - [self addStringValueToKeychain:self.userName forKey:kBITFeedbackName]; - - if (self.userEmail) - [self addStringValueToKeychain:self.userEmail forKey:kBITFeedbackEmail]; - - if (self.lastCheck) - [archiver encodeObject:self.lastCheck forKey:kBITFeedbackDateOfLastCheck]; - - if (self.lastMessageID) - [archiver encodeObject:self.lastMessageID forKey:kBITFeedbackLastMessageID]; - - [archiver encodeObject:self.feedbackList forKey:kBITFeedbackMessages]; - - [archiver finishEncoding]; - [data writeToFile:self.settingsFile atomically:YES]; -} - - -- (void)updateDidAskUserData { - if (!self.didAskUserData) { - self.didAskUserData = YES; - - [self saveMessages]; - } -} - -#pragma mark - Messages - -- (void)sortFeedbackList { - [self.feedbackList sortUsingComparator:^(BITFeedbackMessage *obj1, BITFeedbackMessage *obj2) { - NSDate *date1 = [obj1 date]; - NSDate *date2 = [obj2 date]; - - // not send, in conflict and send in progress messages on top, sorted by date - // read and unread on bottom, sorted by date - // archived on the very bottom - - if ([obj1 status] >= BITFeedbackMessageStatusSendInProgress && [obj2 status] < BITFeedbackMessageStatusSendInProgress) { - return NSOrderedDescending; - } else if ([obj1 status] < BITFeedbackMessageStatusSendInProgress && [obj2 status] >= BITFeedbackMessageStatusSendInProgress) { - return NSOrderedAscending; - } else if ([obj1 status] == BITFeedbackMessageStatusArchived && [obj2 status] < BITFeedbackMessageStatusArchived) { - return NSOrderedDescending; - } else if ([obj1 status] < BITFeedbackMessageStatusArchived && [obj2 status] == BITFeedbackMessageStatusArchived) { - return NSOrderedAscending; - } else { - return (NSInteger) [date2 compare:date1]; - } - }]; -} - -- (NSUInteger)numberOfMessages { - return [self.feedbackList count]; -} - -- (BITFeedbackMessage *)messageAtIndex:(NSUInteger)index { - if ([self.feedbackList count] > index) { - return [self.feedbackList objectAtIndex:index]; - } - - return nil; -} - -- (BITFeedbackMessage *)messageWithID:(NSNumber *)messageID { - __block BITFeedbackMessage *message = nil; - - [self.feedbackList enumerateObjectsUsingBlock:^(BITFeedbackMessage *objMessage, NSUInteger __unused messagesIdx, BOOL *stop) { - if ([[objMessage identifier] isEqualToNumber:messageID]) { - message = objMessage; - *stop = YES; - } - }]; - - return message; -} - -- (NSArray *)messagesWithStatus:(BITFeedbackMessageStatus)status { - NSMutableArray *resultMessages = [[NSMutableArray alloc] initWithCapacity:[self.feedbackList count]]; - - [self.feedbackList enumerateObjectsUsingBlock:^(BITFeedbackMessage *objMessage, NSUInteger __unused messagesIdx, BOOL __unused *stop) { - if ([objMessage status] == status) { - [resultMessages addObject:objMessage]; - } - }]; - - return [NSArray arrayWithArray:resultMessages];; -} - -- (BITFeedbackMessage *)lastMessageHavingID { - __block BITFeedbackMessage *message = nil; - - - // Note: the logic here is slightly different than in our mac SDK, as self.feedbackList is sorted in different order. - // Compare the implementation of - (void)sortFeedbackList; in both SDKs. - [self.feedbackList enumerateObjectsUsingBlock:^(BITFeedbackMessage *objMessage, NSUInteger __unused messagesIdx, BOOL *stop) { - if ([[objMessage identifier] integerValue] != 0) { - message = objMessage; - *stop = YES; - } - }]; - - return message; -} - -- (void)markSendInProgressMessagesAsPending { - // make sure message that may have not been send successfully, get back into the right state to be send again - [self.feedbackList enumerateObjectsUsingBlock:^(id objMessage, NSUInteger __unused messagesIdx, BOOL __unused *stop) { - if ([(BITFeedbackMessage *) objMessage status] == BITFeedbackMessageStatusSendInProgress) - [(BITFeedbackMessage *) objMessage setStatus:BITFeedbackMessageStatusSendPending]; - }]; -} - -- (void)markSendInProgressMessagesAsInConflict { - // make sure message that may have not been send successfully, get back into the right state to be send again - [self.feedbackList enumerateObjectsUsingBlock:^(id objMessage, NSUInteger __unused messagesIdx, BOOL __unused *stop) { - if ([(BITFeedbackMessage *) objMessage status] == BITFeedbackMessageStatusSendInProgress) - [(BITFeedbackMessage *) objMessage setStatus:BITFeedbackMessageStatusInConflict]; - }]; -} - -- (void)updateLastMessageID { - BITFeedbackMessage *lastMessageHavingID = [self lastMessageHavingID]; - if (lastMessageHavingID) { - if (!self.lastMessageID || [self.lastMessageID compare:[lastMessageHavingID identifier]] != NSOrderedSame) - self.lastMessageID = [lastMessageHavingID identifier]; - } -} - -- (BOOL)deleteMessageAtIndex:(NSUInteger)index { - if (self.feedbackList && [self.feedbackList count] > index && [self.feedbackList objectAtIndex:index]) { - BITFeedbackMessage *message = self.feedbackList[index]; - [message deleteContents]; - [self.feedbackList removeObjectAtIndex:index]; - - [self saveMessages]; - return YES; - } - - return NO; -} - -- (void)deleteAllMessages { - [self.feedbackList removeAllObjects]; - - [self saveMessages]; -} - -- (BOOL)shouldForceNewThread { - id strongDelegate = self.delegate; - if (strongDelegate && [strongDelegate respondsToSelector:@selector(forceNewFeedbackThreadForFeedbackManager:)]) { - return [strongDelegate forceNewFeedbackThreadForFeedbackManager:self]; - } else { - return NO; - } -} - - -#pragma mark - User - -- (BOOL)askManualUserDataAvailable { - [self updateAppDefinedUserData]; - - if (self.requireUserName == BITFeedbackUserDataElementDontShow && - self.requireUserEmail == BITFeedbackUserDataElementDontShow) - return NO; - - return YES; -} - -- (BOOL)requireManualUserDataMissing { - [self updateAppDefinedUserData]; - - if (self.requireUserName == BITFeedbackUserDataElementRequired && !self.userName) - return YES; - - if (self.requireUserEmail == BITFeedbackUserDataElementRequired && !self.userEmail) - return YES; - - return NO; -} - -- (BOOL)isManualUserDataAvailable { - [self updateAppDefinedUserData]; - - if ((self.requireUserName != BITFeedbackUserDataElementDontShow && self.userName) || - (self.requireUserEmail != BITFeedbackUserDataElementDontShow && self.userEmail)) - return YES; - - return NO; -} - - -#pragma mark - Networking - -- (void)updateMessageListFromResponse:(NSDictionary *)jsonDictionary { - if (!jsonDictionary) { - // nil is used when the server returns 404, so we need to mark all existing threads as archives and delete the discussion token - - NSArray *messagesSendInProgress = [self messagesWithStatus:BITFeedbackMessageStatusSendInProgress]; - NSInteger pendingMessagesCount = [messagesSendInProgress count] + [[self messagesWithStatus:BITFeedbackMessageStatusSendPending] count]; - - [self markSendInProgressMessagesAsPending]; - - [self.feedbackList enumerateObjectsUsingBlock:^(id objMessage, NSUInteger __unused messagesIdx, BOOL __unused *stop) { - if ([(BITFeedbackMessage *) objMessage status] != BITFeedbackMessageStatusSendPending) - [(BITFeedbackMessage *) objMessage setStatus:BITFeedbackMessageStatusArchived]; - }]; - - if ([self token]) { - self.token = nil; - } - - NSInteger pendingMessagesCountAfterProcessing = [[self messagesWithStatus:BITFeedbackMessageStatusSendPending] count]; - - [self saveMessages]; - - // check if this request was successful and we have more messages pending and continue if positive - if (pendingMessagesCount > pendingMessagesCountAfterProcessing && pendingMessagesCountAfterProcessing > 0) { - [self performSelector:@selector(submitPendingMessages) withObject:nil afterDelay:0.1]; - } - - return; - } - - NSDictionary *feedback = [jsonDictionary objectForKey:@"feedback"]; - NSString *token = [jsonDictionary objectForKey:@"token"]; - NSDictionary *feedbackObject = [jsonDictionary objectForKey:@"feedback"]; - if (feedback && token && feedbackObject) { - if ([self shouldForceNewThread]) { - self.token = nil; - } else { - // update the thread token, which is not available until the 1st message was successfully sent - self.token = token; - } - - self.lastCheck = [NSDate date]; - - // add all new messages - NSArray *feedMessages = [feedbackObject objectForKey:@"messages"]; - - // get the message that was currently sent if available - NSArray *messagesSendInProgress = [self messagesWithStatus:BITFeedbackMessageStatusSendInProgress]; - - NSInteger pendingMessagesCount = [messagesSendInProgress count] + [[self messagesWithStatus:BITFeedbackMessageStatusSendPending] count]; - - __block BOOL newMessage = NO; - NSMutableSet *returnedMessageIDs = [[NSMutableSet alloc] init]; - - [feedMessages enumerateObjectsUsingBlock:^(id objMessage, NSUInteger __unused messagesIdx, BOOL __unused *stop) { - if ([(NSDictionary *) objMessage objectForKey:@"id"]) { - NSNumber *messageID = [(NSDictionary *) objMessage objectForKey:@"id"]; - [returnedMessageIDs addObject:messageID]; - - BITFeedbackMessage *thisMessage = [self messageWithID:messageID]; - if (!thisMessage) { - // check if this is a message that was sent right now - __block BITFeedbackMessage *matchingSendInProgressOrInConflictMessage = nil; - - // TODO: match messages in state conflict - - [messagesSendInProgress enumerateObjectsUsingBlock:^(id objSendInProgressMessage, NSUInteger __unused messagesSendInProgressIdx, BOOL *stop2) { - if ([[(NSDictionary *) objMessage objectForKey:@"token"] isEqualToString:[(BITFeedbackMessage *) objSendInProgressMessage token]]) { - matchingSendInProgressOrInConflictMessage = objSendInProgressMessage; - *stop2 = YES; - } - }]; - - if (matchingSendInProgressOrInConflictMessage) { - matchingSendInProgressOrInConflictMessage.date = [self parseRFC3339Date:[(NSDictionary *) objMessage objectForKey:@"created_at"]]; - matchingSendInProgressOrInConflictMessage.identifier = messageID; - matchingSendInProgressOrInConflictMessage.status = BITFeedbackMessageStatusRead; - NSArray *feedbackAttachments = [(NSDictionary *) objMessage objectForKey:@"attachments"]; - if (matchingSendInProgressOrInConflictMessage.attachments.count == feedbackAttachments.count) { - int attachmentIndex = 0; - for (BITFeedbackMessageAttachment *attachment in matchingSendInProgressOrInConflictMessage.attachments) { - attachment.identifier = feedbackAttachments[attachmentIndex][@"id"]; - attachment.sourceURL = feedbackAttachments[attachmentIndex][@"url"]; - attachmentIndex++; - } - } - } else { - if ([(NSDictionary *) objMessage objectForKey:@"clean_text"] || [(NSDictionary *) objMessage objectForKey:@"text"] || [(NSDictionary *) objMessage objectForKey:@"attachments"]) { - BITFeedbackMessage *message = [[BITFeedbackMessage alloc] init]; - message.text = [(NSDictionary *) objMessage objectForKey:@"clean_text"] ?: [(NSDictionary *) objMessage objectForKey:@"text"] ?: @""; - message.name = [(NSDictionary *) objMessage objectForKey:@"name"] ?: @""; - message.email = [(NSDictionary *) objMessage objectForKey:@"email"] ?: @""; - - message.date = [self parseRFC3339Date:[(NSDictionary *) objMessage objectForKey:@"created_at"]] ?: [NSDate date]; - message.identifier = [(NSDictionary *) objMessage objectForKey:@"id"]; - message.status = BITFeedbackMessageStatusUnread; - - for (NSDictionary *attachmentData in objMessage[@"attachments"]) { - BITFeedbackMessageAttachment *newAttachment = [BITFeedbackMessageAttachment new]; - newAttachment.originalFilename = attachmentData[@"file_name"]; - newAttachment.identifier = attachmentData[@"id"]; - newAttachment.sourceURL = attachmentData[@"url"]; - newAttachment.contentType = attachmentData[@"content_type"]; - [message addAttachmentsObject:newAttachment]; - } - - [self.feedbackList addObject:message]; - - newMessage = YES; - } - } - } else { - // we should never get any messages back that are already stored locally, - // since we add the last_message_id to the request - } - } - }]; - - [self markSendInProgressMessagesAsPending]; - - [self sortFeedbackList]; - [self updateLastMessageID]; - - // we got a new incoming message, trigger user notification system - if (newMessage) { - // check if the latest message is from the users own email address, then don't show an alert since they answered using their own email - BOOL latestMessageFromUser = NO; - - BITFeedbackMessage *latestMessage = [self lastMessageHavingID]; - if (self.userEmail && latestMessage.email && [self.userEmail compare:latestMessage.email] == NSOrderedSame) - latestMessageFromUser = YES; - id strongDelegate = self.delegate; - if (!latestMessageFromUser) { - if ([strongDelegate respondsToSelector:@selector(feedbackManagerDidReceiveNewFeedback:)]) { - [strongDelegate feedbackManagerDidReceiveNewFeedback:self]; - } - - if (self.showAlertOnIncomingMessages && !self.currentFeedbackListViewController && !self.currentFeedbackComposeViewController) { - dispatch_async(dispatch_get_main_queue(), ^{ - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:BITHockeyLocalizedString(@"HockeyFeedbackNewMessageTitle") - message:BITHockeyLocalizedString(@"HockeyFeedbackNewMessageText") - preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *cancelAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"HockeyFeedbackIgnore") - style:UIAlertActionStyleCancel - handler:nil]; - UIAlertAction *showAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"HockeyFeedbackShow") - style:UIAlertActionStyleDefault - handler:^(UIAlertAction __unused * __nonnull action) { - [self showFeedbackListView]; - }]; - [alertController addAction:cancelAction]; - [alertController addAction:showAction]; - - [self showAlertController:alertController]; - self.incomingMessagesAlertShowing = YES; - }); - } - } - } - - NSInteger pendingMessagesCountAfterProcessing = [[self messagesWithStatus:BITFeedbackMessageStatusSendPending] count]; - - // check if this request was successful and we have more messages pending and continue if positive - if (pendingMessagesCount > pendingMessagesCountAfterProcessing && pendingMessagesCountAfterProcessing > 0) { - [self performSelector:@selector(submitPendingMessages) withObject:nil afterDelay:0.1]; - } - - } else { - [self markSendInProgressMessagesAsPending]; - } - - [self saveMessages]; - - return; -} - - -- (void)sendNetworkRequestWithHTTPMethod:(NSString *)httpMethod withMessage:(BITFeedbackMessage *)message completionHandler:(void (^)(NSError *error))completionHandler { - NSString *boundary = @"----FOO"; - - self.networkRequestInProgress = YES; - // inform the UI to update its data in case the list is already showing - [[NSNotificationCenter defaultCenter] postNotificationName:BITHockeyFeedbackMessagesLoadingStarted object:nil]; - - NSString *tokenParameter = @""; - if ([self token]) { - tokenParameter = [NSString stringWithFormat:@"/%@", [self token]]; - } - NSMutableString *parameter = [NSMutableString stringWithFormat:@"api/2/apps/%@/feedback%@", [self encodedAppIdentifier], tokenParameter]; - - NSString *lastMessageID = @""; - if (!self.lastMessageID) { - [self updateLastMessageID]; - } - if (self.lastMessageID) { - lastMessageID = [NSString stringWithFormat:@"&last_message_id=%li", (long) [self.lastMessageID integerValue]]; - } - - [parameter appendFormat:@"?format=json&bundle_version=%@&sdk=%@&sdk_version=%@%@", - bit_URLEncodedString([[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]), - BITHOCKEY_NAME, - BITHOCKEY_VERSION, - lastMessageID - ]; - - // build request & send - NSString *url = [NSString stringWithFormat:@"%@%@", self.serverURL, parameter]; - BITHockeyLogDebug(@"INFO: sending api request to %@", url); - - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:(NSURL *)[NSURL URLWithString:url] cachePolicy:1 timeoutInterval:10.0]; - [request setHTTPMethod:httpMethod]; - [request setValue:@"Hockey/iOS" forHTTPHeaderField:@"User-Agent"]; - [request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"]; - - if (message) { - NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary]; - [request setValue:contentType forHTTPHeaderField:@"Content-type"]; - - NSMutableData *postBody = [NSMutableData data]; - - [postBody appendData:[BITHockeyAppClient dataWithPostValue:@"Apple" forKey:@"oem" boundary:boundary]]; - [postBody appendData:[BITHockeyAppClient dataWithPostValue:[[UIDevice currentDevice] systemVersion] forKey:@"os_version" boundary:boundary]]; - [postBody appendData:[BITHockeyAppClient dataWithPostValue:[self getDevicePlatform] forKey:@"model" boundary:boundary]]; - [postBody appendData:[BITHockeyAppClient dataWithPostValue:[[[NSBundle mainBundle] preferredLocalizations] objectAtIndex:0] forKey:@"lang" boundary:boundary]]; - [postBody appendData:[BITHockeyAppClient dataWithPostValue:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"] forKey:@"bundle_version" boundary:boundary]]; - [postBody appendData:[BITHockeyAppClient dataWithPostValue:[message text] forKey:@"text" boundary:boundary]]; - [postBody appendData:[BITHockeyAppClient dataWithPostValue:[message token] forKey:@"message_token" boundary:boundary]]; - - NSString *installString = bit_appAnonID(NO); - if (installString) { - [postBody appendData:[BITHockeyAppClient dataWithPostValue:installString forKey:@"install_string" boundary:boundary]]; - } - - if (self.userID) { - [postBody appendData:[BITHockeyAppClient dataWithPostValue:self.userID forKey:@"user_string" boundary:boundary]]; - } - if (self.userName) { - [postBody appendData:[BITHockeyAppClient dataWithPostValue:self.userName forKey:@"name" boundary:boundary]]; - } - if (self.userEmail) { - [postBody appendData:[BITHockeyAppClient dataWithPostValue:self.userEmail forKey:@"email" boundary:boundary]]; - } - - - NSInteger photoIndex = 0; - - for (BITFeedbackMessageAttachment *attachment in message.attachments) { - NSString *key = [NSString stringWithFormat:@"attachment%ld", (long) photoIndex]; - - NSString *filename = attachment.originalFilename; - - if (!filename) { - filename = [NSString stringWithFormat:@"Attachment %ld", (long) photoIndex]; - } - - [postBody appendData:[BITHockeyAppClient dataWithPostValue:attachment.data forKey:key contentType:attachment.contentType boundary:boundary filename:filename]]; - - photoIndex++; - } - - [postBody appendData:(NSData *)[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; - - - [request setHTTPBody:postBody]; - } - __weak typeof(self) weakSelf = self; - NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; - __block NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration]; - - NSURLSessionDataTask *task = [session dataTaskWithRequest:request - completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - typeof(self) strongSelf = weakSelf; - - [session finishTasksAndInvalidate]; - - [strongSelf handleFeedbackMessageResponse:response data:data error:error completion:completionHandler]; - }]; - [task resume]; - -} - -- (void)handleFeedbackMessageResponse:(NSURLResponse *)response data:(NSData *)responseData error:(NSError *)error completion:(void (^)(NSError *error))completionHandler { - self.networkRequestInProgress = NO; - - if (error) { - [self reportError:error]; - [self markSendInProgressMessagesAsPending]; - if (completionHandler) { - completionHandler(error); - } - } else { - NSInteger statusCode = [(NSHTTPURLResponse *) response statusCode]; - if (statusCode == 404) { - // thread has been deleted, we archive it - [self updateMessageListFromResponse:nil]; - } else if (statusCode == 409) { - // we submitted a message that is already on the server, mark it as being in conflict and resolve it with another fetch - - if (!self.token) { - // set the token to the first message token, since this is identical - __block NSString *token = nil; - - [self.feedbackList enumerateObjectsUsingBlock:^(id objMessage, NSUInteger __unused messagesIdx, BOOL *stop) { - if ([(BITFeedbackMessage *) objMessage status] == BITFeedbackMessageStatusSendInProgress) { - token = [(BITFeedbackMessage *) objMessage token]; - *stop = YES; - } - }]; - - if (token) { - self.token = token; - } - } - - [self markSendInProgressMessagesAsInConflict]; - [self saveMessages]; - [self performSelector:@selector(fetchMessageUpdates) withObject:nil afterDelay:0.2]; - } else if ([responseData length]) { - NSString *responseString = [[NSString alloc] initWithBytes:[responseData bytes] length:[responseData length] encoding:NSUTF8StringEncoding]; - BITHockeyLogDebug(@"INFO: Received API response: %@", responseString); - - if (responseString && [responseString dataUsingEncoding:NSUTF8StringEncoding]) { - NSError *localError = NULL; - - NSDictionary *feedDict = (NSDictionary *) [NSJSONSerialization JSONObjectWithData:(NSData *)[responseString dataUsingEncoding:NSUTF8StringEncoding] options:0 error:&localError]; - - // server returned empty response? - if (localError) { - [self reportError:localError]; - } else if (![feedDict count]) { - [self reportError:[NSError errorWithDomain:kBITFeedbackErrorDomain - code:BITFeedbackAPIServerReturnedEmptyResponse - userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Server returned empty response.", NSLocalizedDescriptionKey, nil]]]; - } else { - BITHockeyLogDebug(@"INFO: Received API response: %@", responseString); - NSString *status = [feedDict objectForKey:@"status"]; - if ([status compare:@"success"] != NSOrderedSame) { - [self reportError:[NSError errorWithDomain:kBITFeedbackErrorDomain - code:BITFeedbackAPIServerReturnedInvalidStatus - userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Server returned invalid status.", NSLocalizedDescriptionKey, nil]]]; - } else { - [self updateMessageListFromResponse:feedDict]; - } - } - } - } - - [self markSendInProgressMessagesAsPending]; - if (completionHandler) { - completionHandler(error); - } - } -} - -- (void)fetchMessageUpdates { - if ([self.feedbackList count] == 0 && !self.token) { - // inform the UI to update its data in case the list is already showing - [[NSNotificationCenter defaultCenter] postNotificationName:BITHockeyFeedbackMessagesLoadingFinished object:nil]; - - return; - } - - [self sendNetworkRequestWithHTTPMethod:@"GET" - withMessage:nil - completionHandler:^(NSError __unused *error) { - // inform the UI to update its data in case the list is already showing - [[NSNotificationCenter defaultCenter] postNotificationName:BITHockeyFeedbackMessagesLoadingFinished object:nil]; - }]; -} - -- (void)submitPendingMessages { - if (self.networkRequestInProgress) { - [[self class] cancelPreviousPerformRequestsWithTarget:self selector:@selector(submitPendingMessages) object:nil]; - [self performSelector:@selector(submitPendingMessages) withObject:nil afterDelay:2.0]; - return; - } - - // app defined user data may have changed, update it - [self updateAppDefinedUserData]; - [self saveMessages]; - - NSArray *pendingMessages = [self messagesWithStatus:BITFeedbackMessageStatusSendPending]; - - if ([pendingMessages count] > 0) { - // we send one message at a time - BITFeedbackMessage *messageToSend = pendingMessages[0]; - - [messageToSend setStatus:BITFeedbackMessageStatusSendInProgress]; - if (self.userID) - [messageToSend setUserID:self.userID]; - if (self.userName) - [messageToSend setName:self.userName]; - if (self.userEmail) - [messageToSend setEmail:self.userEmail]; - - NSString *httpMethod = @"POST"; - if ([self token]) { - httpMethod = @"PUT"; - } - - [self sendNetworkRequestWithHTTPMethod:httpMethod - withMessage:messageToSend - completionHandler:^(NSError *error) { - if (error) { - [self markSendInProgressMessagesAsPending]; - [self saveMessages]; - } - - // inform the UI to update its data in case the list is already showing - [[NSNotificationCenter defaultCenter] postNotificationName:BITHockeyFeedbackMessagesLoadingFinished object:nil]; - }]; - } -} - -- (void)submitMessageWithText:(NSString *)text andAttachments:(NSArray *)attachments { - BITFeedbackMessage *message = [[BITFeedbackMessage alloc] init]; - message.text = text; - [message setStatus:BITFeedbackMessageStatusSendPending]; - [message setToken:[self uuidAsLowerCaseAndShortened]]; - [message setAttachments:attachments]; - [message setUserMessage:YES]; - - [self.feedbackList addObject:message]; - - [self submitPendingMessages]; -} - -#pragma mark - Observation Handling - -- (void)setFeedbackObservationMode:(BITFeedbackObservationMode)feedbackObservationMode { - //Ignore if feedback manager is disabled - if ([self isFeedbackManagerDisabled]) return; - - if (feedbackObservationMode != _feedbackObservationMode) { - _feedbackObservationMode = feedbackObservationMode; - - // Reset the other observation modes. - if (feedbackObservationMode == BITFeedbackObservationNone) { - if (self.observationModeOnScreenshotEnabled) { - [self setObservationModeOnScreenshotEnabled:NO]; - } - if (self.observationModeThreeFingerTapEnabled) { - [self setObservationModeThreeFingerTapEnabled:NO]; - } - BITHockeyLogVerbose(@"Set feedbackObservationMode to BITFeedbackObservationNone"); - } - - if (feedbackObservationMode == BITFeedbackObservationModeOnScreenshot) { - [self setObservationModeOnScreenshotEnabled:YES]; - if (self.observationModeThreeFingerTapEnabled) { - [self setObservationModeThreeFingerTapEnabled:NO]; - } - } - - if (feedbackObservationMode == BITFeedbackObservationModeThreeFingerTap) { - [self setObservationModeThreeFingerTapEnabled:YES]; - if (self.observationModeOnScreenshotEnabled) { - [self setObservationModeOnScreenshotEnabled:NO]; - } - } - - if (feedbackObservationMode == BITFeedbackObservationModeAll) { - [self setObservationModeOnScreenshotEnabled:YES]; - [self setObservationModeThreeFingerTapEnabled:YES]; - } - } -} - -- (void)setObservationModeOnScreenshotEnabled:(BOOL)observationModeOnScreenshotEnabled { - // Enable/disable screenshot notification - if (observationModeOnScreenshotEnabled) { - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(screenshotNotificationReceived:) name:UIApplicationUserDidTakeScreenshotNotification object:nil]; - BITHockeyLogVerbose(@"Added observer for UIApplocationUserDidTakeScreenshotNotification."); - } else { - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationUserDidTakeScreenshotNotification object:nil]; - BITHockeyLogVerbose(@"Removed observer for UIApplocationUserDidTakeScreenshotNotification."); - } - - _observationModeOnScreenshotEnabled = observationModeOnScreenshotEnabled; - - BITHockeyLogVerbose(@"Enabled BITFeedbackObservationModeOnScreenshot."); -} - -- (void)setObservationModeThreeFingerTapEnabled:(BOOL)observationModeThreeFingerTapEnabled { - _observationModeThreeFingerTapEnabled = observationModeThreeFingerTapEnabled; - - if (observationModeThreeFingerTapEnabled) { - if (!self.tapRecognizer) { - self.tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(screenshotTripleTap:)]; - self.tapRecognizer.numberOfTouchesRequired = 3; - self.tapRecognizer.delegate = self; - - dispatch_async(dispatch_get_main_queue(), ^{ - if (self.tapRecognizer) { - [[UIApplication sharedApplication].keyWindow addGestureRecognizer:self.tapRecognizer]; - } - }); - } - - BITHockeyLogVerbose(@"Enabled BITFeedbackObservationModeThreeFingerTap."); - } else { - [[[UIApplication sharedApplication] keyWindow] removeGestureRecognizer:self.tapRecognizer]; - self.tapRecognizer = nil; - BITHockeyLogVerbose(@"Disabled BITFeedbackObservationModeThreeFingerTap."); - } -} - -- (void)screenshotNotificationReceived:(NSNotification *) __unused notification { - // Don't do anything if FeedbackManager was disabled. - if ([self isFeedbackManagerDisabled]) return; - - double amountOfSeconds = 1.5; - dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (amountOfSeconds * NSEC_PER_SEC)); - - dispatch_after(delayTime, dispatch_get_main_queue(), ^{ - [self extractLastPictureFromLibraryAndLaunchFeedback]; - }); -} - -- (void)extractLastPictureFromLibraryAndLaunchFeedback { - [self requestLatestImageWithCompletionHandler:^(UIImage *latestImage) { - [self showFeedbackComposeViewWithPreparedItems:@[latestImage]]; - }]; -} - -- (void)requestLatestImageWithCompletionHandler:(BITLatestImageFetchCompletionBlock)completionHandler { - if (!completionHandler) {return;} - - // Safeguard in case the dev hasn't set the NSPhotoLibraryUsageDescription in their Info.plist - if (![self isiOS10PhotoPolicySet]) {return;} - - [self fetchLatestImageUsingPhotoLibraryWithCompletionHandler:completionHandler]; -} - -- (void)fetchLatestImageUsingPhotoLibraryWithCompletionHandler:(BITLatestImageFetchCompletionBlock)completionHandler { - // Safeguard in case the dev hasn't set the NSPhotoLibraryUsageDescription in their Info.plist - if (![self isiOS10PhotoPolicySet]) {return;} - - [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { - switch (status) { - case PHAuthorizationStatusDenied: - case PHAuthorizationStatusRestricted: - BITHockeyLogDebug(@"INFO: The latest image could not be fetched, no permissions."); - break; - - case PHAuthorizationStatusAuthorized: - [self loadLatestImageAssetWithCompletionHandler:completionHandler]; - break; - case PHAuthorizationStatusNotDetermined: - BITHockeyLogDebug(@"INFO: The Photo Library authorization status is undetermined. This should not happen."); - break; - } - }]; -} - -- (void)loadLatestImageAssetWithCompletionHandler:(BITLatestImageFetchCompletionBlock)completionHandler { - - // Safeguard in case the dev hasn't set the NSPhotoLibraryUsageDescription in their Info.plist - if (![self isiOS10PhotoPolicySet]) {return;} - - PHImageManager *imageManager = PHImageManager.defaultManager; - - PHFetchOptions *fetchOptions = [PHFetchOptions new]; - fetchOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES]]; - - PHFetchResult *fetchResult = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:fetchOptions]; - - if (fetchResult.count > 0) { - PHAsset *latestImageAsset = (PHAsset *) fetchResult.lastObject; - if (latestImageAsset) { - PHImageRequestOptions *options = [PHImageRequestOptions new]; - options.version = PHImageRequestOptionsVersionOriginal; - options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat; - options.resizeMode = PHImageRequestOptionsResizeModeNone; - - [imageManager requestImageDataForAsset:latestImageAsset options:options resultHandler:^(NSData *_Nullable imageData, NSString *_Nullable __unused dataUTI, UIImageOrientation __unused orientation, NSDictionary *_Nullable __unused info) { - if (imageData) { - completionHandler((UIImage *)[UIImage imageWithData:(NSData *)imageData]); - } else { - BITHockeyLogDebug(@"INFO: The latest image could not be fetched, requested image data was empty."); - } - }]; - } - } else { - BITHockeyLogDebug(@"INFO: The latest image could not be fetched, the fetch result was empty."); - } -} - -- (void)screenshotTripleTap:(UITapGestureRecognizer *)tapRecognizer { - // Don't do anything if FeedbackManager was disabled. - if ([self isFeedbackManagerDisabled]) return; - - if (tapRecognizer.state == UIGestureRecognizerStateRecognized) { - [self showFeedbackComposeViewWithGeneratedScreenshot]; - } -} - -- (BOOL)gestureRecognizer:(UIGestureRecognizer *) __unused gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *) __unused otherGestureRecognizer { - return YES; -} - -@end - - -#endif /* HOCKEYSDK_FEATURE_FEEDBACK */ diff --git a/submodules/HockeySDK-iOS/Classes/BITFeedbackManagerDelegate.h b/submodules/HockeySDK-iOS/Classes/BITFeedbackManagerDelegate.h deleted file mode 100644 index 9afb889462..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITFeedbackManagerDelegate.h +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Authors: Stephan Diederich, Benjamin Scholtysik - * - * Copyright (c) 2013-2016 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import -#import "BITFeedbackComposeViewControllerDelegate.h" - -#import "HockeySDKNullability.h" -NS_ASSUME_NONNULL_BEGIN - -@class BITFeedbackManager; - -/** - * Delegate protocol which is notified about changes in the feedbackManager - * @TODO - * * move shouldShowUpdateAlert from feedbackManager here - */ -@protocol BITFeedbackManagerDelegate - -@optional - -/** - * Can be implemented to control wether the feedback manager should automatically - * fetch for new messages on app startup or when becoming active. - * - * By default the SDK fetches on app startup or when the app is becoming active again - * if there are already messages existing or pending on the device. - * - * You could disable it e.g. depending on available mobile network/WLAN connection - * or let it fetch less frequently. - * - * @param feedbackManager The feedbackManager which did detect the new messages - */ -- (BOOL)allowAutomaticFetchingForNewFeedbackForManager:(BITFeedbackManager *)feedbackManager; - - -/** - * can be implemented to know when new feedback from the server arrived - * - * @param feedbackManager The feedbackManager which did detect the new messages - */ -- (void)feedbackManagerDidReceiveNewFeedback:(BITFeedbackManager *)feedbackManager; - - -/** - * This optional method can be implemented to provide items to prefill - * the FeedbackComposeMessage user interface with the given items. - * - * If the user sends the feedback message, these items will be attached to that message. - * - * All NSString-Content in the array will be concatenated and result in the message, - * while all UIImage and NSData-instances will be turned into attachments. - * - * @param feedbackManager The BITFeedbackManager instance that will handle sending the feedback. - * - * @return An array containing the items to be attached to the feedback message - * @see `[BITFeedbackComposeViewController prepareWithItems:] - */ -- (nullable NSArray *)preparedItemsForFeedbackManager:(BITFeedbackManager *)feedbackManager; - -/** - * Indicates if a new thread should be created for each new feedback message - * - * Setting it to `YES` will force a new thread whenever a new message is sent as - * opposed to the default resume thread behaviour. - * - * @return A BOOL indicating if each feedback message should be sent as a new thread. - */ -- (BOOL)forceNewFeedbackThreadForFeedbackManager:(BITFeedbackManager *)feedbackManager; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/HockeySDK-iOS/Classes/BITFeedbackManagerPrivate.h b/submodules/HockeySDK-iOS/Classes/BITFeedbackManagerPrivate.h deleted file mode 100644 index ab4bbe1935..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITFeedbackManagerPrivate.h +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Author: Andreas Linde - * Kent Sutherland - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * Copyright (c) 2011 Andreas Linde & Kent Sutherland. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#if HOCKEYSDK_FEATURE_FEEDBACK - -extern NSString *const kBITFeedbackUpdateAttachmentThumbnail; - -#import "BITFeedbackMessage.h" - -@class UITapGestureRecognizer; - -@interface BITFeedbackManager () { -} - - -///----------------------------------------------------------------------------- -/// @name Delegate -///----------------------------------------------------------------------------- - -/** - Sets the `BITFeedbackManagerDelegate` delegate. - - Can be set to be notified when new feedback is received from the server. - - The delegate is automatically set by using `[BITHockeyManager setDelegate:]`. You - should not need to set this delegate individually. - - @see `[BITHockeyManager setDelegate:]` - */ -@property (nonatomic, weak) id delegate; - - -@property (nonatomic, strong) NSMutableArray *feedbackList; -@property (nonatomic, copy) NSString *token; - - -// used by BITHockeyManager if disable status is changed -@property (nonatomic, getter = isFeedbackManagerDisabled) BOOL disableFeedbackManager; -// TapRecognizer used in case feedback observation mode is BITFeedbackObservationModeThreeFingerTap is set. -@property(nonatomic, strong) UITapGestureRecognizer *tapRecognizer; -@property(nonatomic) BOOL observationModeOnScreenshotEnabled; -@property(nonatomic) BOOL observationModeThreeFingerTapEnabled; - - -@property (nonatomic, strong) BITFeedbackListViewController *currentFeedbackListViewController; -@property (nonatomic, strong) BITFeedbackComposeViewController *currentFeedbackComposeViewController; -@property (nonatomic) BOOL didAskUserData; - -@property (nonatomic, strong) NSDate *lastCheck; - -@property (nonatomic, strong) NSNumber *lastMessageID; - -@property (nonatomic, copy) NSString *userID; -@property (nonatomic, copy) NSString *userName; -@property (nonatomic, copy) NSString *userEmail; - - - -// Fetch user meta data -- (BOOL)updateUserIDUsingKeychainAndDelegate; -- (BOOL)updateUserNameUsingKeychainAndDelegate; -- (BOOL)updateUserEmailUsingKeychainAndDelegate; - -// check if the user wants to influence when fetching of new messages may be done -- (BOOL)allowFetchingNewMessages; - -// load new messages from the server -- (void)updateMessagesList; - -// load new messages from the server if the last request is too long ago -- (void)updateMessagesListIfRequired; - -- (NSUInteger)numberOfMessages; -- (BITFeedbackMessage *)messageAtIndex:(NSUInteger)index; - -- (void)submitMessageWithText:(NSString *)text andAttachments:(NSArray *)photos; -- (void)submitPendingMessages; - -// Returns YES if manual user data can be entered, required or optional -- (BOOL)askManualUserDataAvailable; - -// Returns YES if required user data is missing? -- (BOOL)requireManualUserDataMissing; - -// Returns YES if user data is available and can be edited -- (BOOL)isManualUserDataAvailable; - -// used in the user data screen -- (void)updateDidAskUserData; - - -- (BITFeedbackMessage *)messageWithID:(NSNumber *)messageID; - -- (NSArray *)messagesWithStatus:(BITFeedbackMessageStatus)status; - -- (void)saveMessages; - -- (void)fetchMessageUpdates; -- (void)updateMessageListFromResponse:(NSDictionary *)jsonDictionary; - -- (BOOL)deleteMessageAtIndex:(NSUInteger)index; -- (void)deleteAllMessages; - -@end - -#endif /* HOCKEYSDK_FEATURE_FEEDBACK */ diff --git a/submodules/HockeySDK-iOS/Classes/BITFeedbackMessage.h b/submodules/HockeySDK-iOS/Classes/BITFeedbackMessage.h deleted file mode 100644 index c33caa983d..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITFeedbackMessage.h +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - - -#import - -@class BITFeedbackMessageAttachment; - -/** - * Status for each feedback message - */ -typedef NS_ENUM(NSInteger, BITFeedbackMessageStatus) { - /** - * default and new messages from SDK per default - */ - BITFeedbackMessageStatusSendPending = 0, - /** - * message is in conflict, happens if the message is already stored on the server and tried sending it again - */ - BITFeedbackMessageStatusInConflict = 1, - /** - * sending of message is in progress - */ - BITFeedbackMessageStatusSendInProgress = 2, - /** - * new messages from server - */ - BITFeedbackMessageStatusUnread = 3, - /** - * messages from server once read and new local messages once successful send from SDK - */ - BITFeedbackMessageStatusRead = 4, - /** - * message is archived, happens if the thread is deleted from the server - */ - BITFeedbackMessageStatusArchived = 5 -}; - -/** - * An individual feedback message - */ -@interface BITFeedbackMessage : NSObject { -} - -@property (nonatomic, copy) NSString *text; -@property (nonatomic, copy) NSString *userID; -@property (nonatomic, copy) NSString *name; -@property (nonatomic, copy) NSString *email; -@property (nonatomic, copy) NSDate *date; -@property (nonatomic, copy) NSNumber *identifier; -@property (nonatomic, copy) NSString *token; -@property (nonatomic, strong) NSArray *attachments; -@property (nonatomic) BITFeedbackMessageStatus status; -@property (nonatomic) BOOL userMessage; - -/** - Delete local cached attachment data - - @warning This method must be called before a feedback message is deleted. - */ -- (void)deleteContents; - -/** - Add an attachment to a message - - @param object BITFeedbackMessageAttachment instance representing the attachment that should be added - */ -- (void)addAttachmentsObject:(BITFeedbackMessageAttachment *)object; - -/** - Return the attachments that can be viewed - - @return NSArray containing the attachment objects that can be previewed - */ -- (NSArray *)previewableAttachments; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITFeedbackMessage.m b/submodules/HockeySDK-iOS/Classes/BITFeedbackMessage.m deleted file mode 100644 index e0c05c4055..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITFeedbackMessage.m +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_FEEDBACK - -#import "BITFeedbackMessage.h" -#import "BITFeedbackMessageAttachment.h" - -@implementation BITFeedbackMessage - - -#pragma mark - NSObject - -- (instancetype) init { - if ((self = [super init])) { - _text = nil; - _userID = nil; - _name = nil; - _email = nil; - _date = [[NSDate alloc] init]; - _token = nil; - _attachments = nil; - _identifier = [[NSNumber alloc] initWithInteger:0]; - _status = BITFeedbackMessageStatusSendPending; - _userMessage = NO; - } - return self; -} - - -#pragma mark - NSCoder - -- (void)encodeWithCoder:(NSCoder *)encoder { - [encoder encodeObject:self.text forKey:@"text"]; - [encoder encodeObject:self.userID forKey:@"userID"]; - [encoder encodeObject:self.name forKey:@"name"]; - [encoder encodeObject:self.email forKey:@"email"]; - [encoder encodeObject:self.date forKey:@"date"]; - [encoder encodeObject:self.identifier forKey:@"id"]; - [encoder encodeObject:self.attachments forKey:@"attachments"]; - [encoder encodeInteger:self.status forKey:@"status"]; - [encoder encodeBool:self.userMessage forKey:@"userMessage"]; - [encoder encodeObject:self.token forKey:@"token"]; -} - -- (instancetype)initWithCoder:(NSCoder *)decoder { - if ((self = [self init])) { - self.text = [decoder decodeObjectForKey:@"text"]; - self.userID = [decoder decodeObjectForKey:@"userID"]; - self.name = [decoder decodeObjectForKey:@"name"]; - self.email = [decoder decodeObjectForKey:@"email"]; - self.date = [decoder decodeObjectForKey:@"date"]; - self.identifier = [decoder decodeObjectForKey:@"id"]; - self.attachments = [decoder decodeObjectForKey:@"attachments"]; - self.status = (BITFeedbackMessageStatus)[decoder decodeIntegerForKey:@"status"]; - self.userMessage = [decoder decodeBoolForKey:@"userMessage"]; - self.token = [decoder decodeObjectForKey:@"token"]; - } - return self; -} - -#pragma mark - Deletion - -- (void)deleteContents { - for (BITFeedbackMessageAttachment *attachment in self.attachments){ - [attachment deleteContents]; - } -} - -- (NSArray *)previewableAttachments { - NSMutableArray *returnArray = [NSMutableArray new]; - - for (BITFeedbackMessageAttachment *attachment in self.attachments){ - if ([QLPreviewController canPreviewItem:attachment ]){ - [returnArray addObject:attachment]; - } - } - - return returnArray; -} - -- (void)addAttachmentsObject:(BITFeedbackMessageAttachment *)object{ - if (!self.attachments){ - self.attachments = [NSArray array]; - } - self.attachments = [self.attachments arrayByAddingObject:object]; -} - - -@end - -#endif /* HOCKEYSDK_FEATURE_FEEDBACK */ diff --git a/submodules/HockeySDK-iOS/Classes/BITFeedbackMessageAttachment.h b/submodules/HockeySDK-iOS/Classes/BITFeedbackMessageAttachment.h deleted file mode 100644 index bdfaec0085..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITFeedbackMessageAttachment.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Author: Moritz Haarmann - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import -#import -#import - -/** - * An individual feedback message attachment - */ -@interface BITFeedbackMessageAttachment : NSObject - -@property (nonatomic, copy) NSNumber *identifier; -@property (nonatomic, copy) NSString *originalFilename; -@property (nonatomic, copy) NSString *contentType; -@property (nonatomic, copy) NSString *sourceURL; -@property (nonatomic) BOOL isLoading; -@property (nonatomic, readonly) NSData *data; - - -@property (nonatomic, readonly) UIImage *imageRepresentation; - - -+ (BITFeedbackMessageAttachment *)attachmentWithData:(NSData *)data contentType:(NSString *)contentType; - -- (UIImage *)thumbnailWithSize:(CGSize)size; - -- (void)replaceData:(NSData *)data; - -- (void)deleteContents; - -- (BOOL)needsLoadingFromURL; - -- (BOOL)isImage; - -- (NSURL *)localURL; - -/** - Used to determine whether QuickLook can preview this file or not. If not, we don't download it. - */ -- (NSString*)possibleFilename; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITFeedbackMessageAttachment.m b/submodules/HockeySDK-iOS/Classes/BITFeedbackMessageAttachment.m deleted file mode 100644 index 1ea27ed79d..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITFeedbackMessageAttachment.m +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Author: Moritz Haarmann - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_FEEDBACK - -#import "BITFeedbackMessageAttachment.h" -#import "BITHockeyHelper.h" -#import "HockeySDKPrivate.h" -#import - -#define kCacheFolderName @"attachments" - -@interface BITFeedbackMessageAttachment() - -@property (nonatomic, strong) NSMutableDictionary *thumbnailRepresentations; -@property (nonatomic, strong) NSData *internalData; -@property (nonatomic, copy) NSString *filename; -@property (nonatomic, copy) NSString *tempFilename; -@property (nonatomic, copy) NSString *cachePath; -@property (nonatomic, strong) NSFileManager *fm; - -@end - -@implementation BITFeedbackMessageAttachment - -+ (BITFeedbackMessageAttachment *)attachmentWithData:(NSData *)data contentType:(NSString *)contentType { - - static NSDateFormatter *formatter; - - if(!formatter) { - formatter = [NSDateFormatter new]; - formatter.dateStyle = NSDateFormatterShortStyle; - formatter.timeStyle = NSDateFormatterShortStyle; - } - - BITFeedbackMessageAttachment *newAttachment = [BITFeedbackMessageAttachment new]; - newAttachment.contentType = contentType; - newAttachment.data = data; - newAttachment.originalFilename = [NSString stringWithFormat:@"Attachment: %@", [formatter stringFromDate:[NSDate date]]]; - - return newAttachment; -} - -- (instancetype)init { - if ((self = [super init])) { - _isLoading = NO; - _thumbnailRepresentations = [NSMutableDictionary new]; - - _fm = [[NSFileManager alloc] init]; - _cachePath = [bit_settingsDir() stringByAppendingPathComponent:kCacheFolderName]; - - BOOL isDirectory; - - if (![_fm fileExistsAtPath:_cachePath isDirectory:&isDirectory]){ - [_fm createDirectoryAtPath:_cachePath withIntermediateDirectories:YES attributes:nil error:nil]; - } - - } - return self; -} - -- (void)setData:(NSData *)data { - self.internalData = data; - self.filename = [self possibleFilename]; - [self.internalData writeToFile:self.filename atomically:YES]; -} - -- (NSData *)data { - if (!self.internalData && self.filename) { - self.internalData = [NSData dataWithContentsOfFile:self.filename]; - } - - if (self.internalData) { - return self.internalData; - } - - return [NSData data]; -} - -- (void)replaceData:(NSData *)data { - self.data = data; - self.thumbnailRepresentations = [NSMutableDictionary new]; -} - -- (BOOL)needsLoadingFromURL { - return (self.sourceURL && ![self.fm fileExistsAtPath:(NSString *)[self.localURL path]]); -} - -- (BOOL)isImage { - return ([self.contentType rangeOfString:@"image"].location != NSNotFound); -} - -- (NSURL *)localURL { - if (self.filename && [self.fm fileExistsAtPath:self.filename]) { - return [NSURL fileURLWithPath:self.filename]; - } - - return [NSURL URLWithString:@""]; -} - - -#pragma mark NSCoding - -- (void)encodeWithCoder:(NSCoder *)aCoder { - [aCoder encodeObject:self.contentType forKey:@"contentType"]; - [aCoder encodeObject:self.filename forKey:@"filename"]; - [aCoder encodeObject:self.originalFilename forKey:@"originalFilename"]; - [aCoder encodeObject:self.sourceURL forKey:@"url"]; -} - -- (instancetype)initWithCoder:(NSCoder *)aDecoder { - if ((self = [self init])) { - self.contentType = [aDecoder decodeObjectForKey:@"contentType"]; - self.filename = [aDecoder decodeObjectForKey:@"filename"]; - self.thumbnailRepresentations = [NSMutableDictionary new]; - self.originalFilename = [aDecoder decodeObjectForKey:@"originalFilename"]; - self.sourceURL = [aDecoder decodeObjectForKey:@"url"]; - } - - return self; -} - - -#pragma mark - Thumbnails / Image Representation - -- (UIImage *)imageRepresentation { - if ([self.contentType rangeOfString:@"image"].location != NSNotFound && self.filename ) { - return [UIImage imageWithData:self.data]; - } else { - // Create a Icon .. - UIDocumentInteractionController* docController = [[UIDocumentInteractionController alloc] init]; - docController.name = self.originalFilename; - NSArray* icons = docController.icons; - if (icons.count){ - return icons[0]; - } else { - return nil; - } - } -} - -- (UIImage *)thumbnailWithSize:(CGSize)size { - id cacheKey = [NSValue valueWithCGSize:size]; - - if (!self.thumbnailRepresentations[cacheKey]) { - UIImage *image = self.imageRepresentation; - // consider the scale. - if (!image) { - return nil; - } - - CGFloat scale = [UIScreen mainScreen].scale; - - if (scale != image.scale) { - - CGSize scaledSize = CGSizeApplyAffineTransform(size, CGAffineTransformMakeScale(scale, scale)); - UIImage *thumbnail = bit_imageToFitSize(image, scaledSize, YES) ; - - UIImage *scaledThumbnail = [UIImage imageWithCGImage:(CGImageRef)thumbnail.CGImage scale:scale orientation:thumbnail.imageOrientation]; - if (thumbnail) { - [self.thumbnailRepresentations setObject:scaledThumbnail forKey:cacheKey]; - } - - } else { - UIImage *thumbnail = bit_imageToFitSize(image, size, YES) ; - - [self.thumbnailRepresentations setObject:thumbnail forKey:cacheKey]; - - } - - } - - return self.thumbnailRepresentations[cacheKey]; -} - - -#pragma mark - Persistence Helpers - -- (void)setFilename:(NSString *)filename { - if (filename) { - filename = [self.cachePath stringByAppendingPathComponent:[filename lastPathComponent]]; - } - _filename = filename; -} - -- (NSString *)possibleFilename { - if (self.tempFilename) { - return self.tempFilename; - } - - NSString *uniqueString = bit_UUID(); - self.tempFilename = [self.cachePath stringByAppendingPathComponent:uniqueString]; - - // File extension that suits the Content type. - - CFStringRef mimeType = (__bridge CFStringRef)self.contentType; - if (mimeType) { - CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType, NULL); - CFStringRef extension = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassFilenameExtension); - if (extension) { - self.tempFilename = [self.tempFilename stringByAppendingPathExtension:(__bridge NSString *)(extension)]; - CFRelease(extension); - } - if (uti) { - CFRelease(uti); - } - } - - return self.tempFilename; -} - -- (void)deleteContents { - if (self.filename) { - [self.fm removeItemAtPath:self.filename error:nil]; - self.filename = nil; - } -} - - -#pragma mark - QLPreviewItem - -- (NSString *)previewItemTitle { - return self.originalFilename; -} - -- (NSURL *)previewItemURL { - if (self.localURL){ - return self.localURL; - } else if (self.sourceURL) { - NSString *filename = self.possibleFilename; - if (filename) { - return [NSURL fileURLWithPath:filename]; - } - } - - return [NSURL URLWithString:(NSString *)[[NSBundle mainBundle] pathForResource:@"FeedbackPlaceholder" ofType:@"png"]]; -} - -@end - -#endif /* HOCKEYSDK_FEATURE_FEEDBACK */ diff --git a/submodules/HockeySDK-iOS/Classes/BITFeedbackUserDataViewController.h b/submodules/HockeySDK-iOS/Classes/BITFeedbackUserDataViewController.h deleted file mode 100644 index 84128d086d..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITFeedbackUserDataViewController.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - - -#import - -@protocol BITFeedbackUserDataDelegate; - -@interface BITFeedbackUserDataViewController : UITableViewController - -@property (nonatomic, weak) id delegate; - -@end - -/////////////////////////////////////////////////////////////////////////////////////////////////// -@protocol BITFeedbackUserDataDelegate - -@required - -// cancel action is invoked -- (void)userDataUpdateCancelled; - -// save action is invoked and all required data available -- (void)userDataUpdateFinished; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITFeedbackUserDataViewController.m b/submodules/HockeySDK-iOS/Classes/BITFeedbackUserDataViewController.m deleted file mode 100644 index 4b49a7e1ee..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITFeedbackUserDataViewController.m +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_FEEDBACK - -#import "HockeySDKPrivate.h" -#import "BITHockeyHelper.h" - -#import "BITFeedbackUserDataViewController.h" -#import "BITFeedbackManagerPrivate.h" - -@interface BITFeedbackUserDataViewController () { -} - -@property (nonatomic, weak) BITFeedbackManager *manager; - -@property (nonatomic, copy) NSString *name; -@property (nonatomic, copy) NSString *email; -@end - - -@implementation BITFeedbackUserDataViewController - - -- (instancetype)initWithStyle:(UITableViewStyle)style { - self = [super initWithStyle:style]; - if (self) { - _delegate = nil; - - _manager = [BITHockeyManager sharedHockeyManager].feedbackManager; - _name = @""; - _email = @""; - } - return self; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - self.title = BITHockeyLocalizedString(@"HockeyFeedbackUserDataTitle"); - - [self.tableView setScrollEnabled:NO]; -} - -- (void)viewWillAppear:(BOOL)animated { - [super viewWillAppear:animated]; - - // Do any additional setup after loading the view. - self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel - target:self - action:@selector(dismissAction:)]; - - self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave - target:self - action:@selector(saveAction:)]; - - BITFeedbackManager *strongManager = self.manager; - if ([strongManager userName]) - self.name = [strongManager userName]; - - if ([strongManager userEmail]) - self.email = [strongManager userEmail]; - - [strongManager updateDidAskUserData]; - - self.navigationItem.rightBarButtonItem.enabled = [self allRequiredFieldsEntered]; -} - -#pragma mark - UIViewController Rotation - -- (UIInterfaceOrientationMask)supportedInterfaceOrientations{ - return UIInterfaceOrientationMaskAll; -} - -#pragma mark - Private methods -- (BOOL)allRequiredFieldsEntered { - BITFeedbackManager *strongManager = self.manager; - if ([strongManager requireUserName] == BITFeedbackUserDataElementRequired && [self.name length] == 0) - return NO; - - if ([strongManager requireUserEmail] == BITFeedbackUserDataElementRequired && [self.email length] == 0) - return NO; - - if ([self.email length] > 0 && !bit_validateEmail(self.email)) - return NO; - - return YES; -} - -- (void)userNameEntered:(id)sender { - self.name = [(UITextField *)sender text]; - - self.navigationItem.rightBarButtonItem.enabled = [self allRequiredFieldsEntered]; -} - -- (void)userEmailEntered:(id)sender { - self.email = [(UITextField *)sender text]; - - self.navigationItem.rightBarButtonItem.enabled = [self allRequiredFieldsEntered]; -} - -- (void)dismissAction:(id) __unused sender { - [self.delegate userDataUpdateCancelled]; -} - -- (void)saveAction:(id) __unused sender { - BITFeedbackManager *strongManager = self.manager; - if ([strongManager requireUserName]) { - [strongManager setUserName:self.name]; - } - - if ([strongManager requireUserEmail]) { - [strongManager setUserEmail:self.email]; - } - - [self.delegate userDataUpdateFinished]; -} - -#pragma mark - Table view data source - -- (NSInteger)numberOfSectionsInTableView:(UITableView *) __unused tableView { - return 1; -} - -- (NSInteger)tableView:(UITableView *) __unused tableView numberOfRowsInSection:(NSInteger) __unused section { - NSInteger rows = 0; - BITFeedbackManager *strongManager = self.manager; - if ([strongManager requireUserName] != BITFeedbackUserDataElementDontShow) - rows ++; - - if ([strongManager requireUserEmail] != BITFeedbackUserDataElementDontShow) - rows ++; - - return rows; -} - -- (NSString *)tableView:(UITableView *) __unused tableView titleForFooterInSection:(NSInteger)section { - if (section == 0) { - return BITHockeyLocalizedString(@"HockeyFeedbackUserDataDescription"); - } - - return nil; -} - -- (UITableViewCell *)tableView:(UITableView *) __unused tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - static NSString *CellIdentifier = @"InputCell"; - BITFeedbackManager *strongManager = self.manager; - UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier]; - if (cell == nil) { - cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; - - cell.accessoryType = UITableViewCellAccessoryNone; - cell.selectionStyle = UITableViewCellSelectionStyleNone; - cell.backgroundColor = [UIColor whiteColor]; - - UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(110, 11, self.view.frame.size.width - 110 - 35, 24)]; - if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad) { - textField.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin; - } - textField.adjustsFontSizeToFitWidth = YES; - textField.textColor = [UIColor blackColor]; - textField.backgroundColor = [UIColor lightGrayColor]; - - if ([indexPath row] == 0 && [strongManager requireUserName] != BITFeedbackUserDataElementDontShow) { - textField.placeholder = BITHockeyLocalizedString(@"HockeyFeedbackUserDataNamePlaceHolder"); - textField.text = self.name; - if (strongManager.requireUserName == BITFeedbackUserDataElementRequired) { - textField.accessibilityHint = BITHockeyLocalizedString(@"HockeyAccessibilityHintRequired"); - } - - textField.keyboardType = UIKeyboardTypeDefault; - if ([strongManager requireUserEmail]) - textField.returnKeyType = UIReturnKeyNext; - else - textField.returnKeyType = UIReturnKeyDone; - textField.autocapitalizationType = UITextAutocapitalizationTypeWords; - [textField addTarget:self action:@selector(userNameEntered:) forControlEvents:UIControlEventEditingChanged]; - [textField becomeFirstResponder]; - } else { - textField.placeholder = BITHockeyLocalizedString(@"HockeyFeedbackUserDataEmailPlaceholder"); - textField.text = self.email; - if (strongManager.requireUserEmail == BITFeedbackUserDataElementRequired) { - textField.accessibilityHint = BITHockeyLocalizedString(@"HockeyAccessibilityHintRequired"); - } - - textField.keyboardType = UIKeyboardTypeEmailAddress; - textField.returnKeyType = UIReturnKeyDone; - textField.autocapitalizationType = UITextAutocapitalizationTypeNone; - [textField addTarget:self action:@selector(userEmailEntered:) forControlEvents:UIControlEventEditingChanged]; - if (![strongManager requireUserName]) - [textField becomeFirstResponder]; - } - - textField.backgroundColor = [UIColor whiteColor]; - textField.autocorrectionType = UITextAutocorrectionTypeNo; - textField.textAlignment = NSTextAlignmentLeft; - textField.delegate = self; - textField.tag = indexPath.row; - - textField.clearButtonMode = UITextFieldViewModeWhileEditing; - [textField setEnabled: YES]; - - [cell addSubview:textField]; - } - - if ([indexPath row] == 0 && [strongManager requireUserName] != BITFeedbackUserDataElementDontShow) { - cell.textLabel.text = BITHockeyLocalizedString(@"HockeyFeedbackUserDataName"); - } else { - cell.textLabel.text = BITHockeyLocalizedString(@"HockeyFeedbackUserDataEmail"); - } - - return cell; -} - - -#pragma mark - UITextFieldDelegate - -- (BOOL)textFieldShouldReturn:(UITextField *)textField { - NSInteger nextTag = textField.tag + 1; - - UIResponder* nextResponder = [self.view viewWithTag:nextTag]; - if (nextResponder) { - [nextResponder becomeFirstResponder]; - } else { - if ([self allRequiredFieldsEntered]) { - if ([textField isFirstResponder]) - [textField resignFirstResponder]; - - [self saveAction:nil]; - } - } - return NO; -} - - -@end - -#endif /* HOCKEYSDK_FEATURE_FEEDBACK */ diff --git a/submodules/HockeySDK-iOS/Classes/BITGZIP.h b/submodules/HockeySDK-iOS/Classes/BITGZIP.h deleted file mode 100644 index dba000867f..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITGZIP.h +++ /dev/null @@ -1,46 +0,0 @@ -// -// GZIP.h -// -// Version 1.0.3 -// -// Created by Nick Lockwood on 03/06/2012. -// Copyright (C) 2012 Charcoal Design -// -// Distributed under the permissive zlib License -// Get the latest version from here: -// -// https://github.com/nicklockwood/GZIP -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// -// 3. This notice may not be removed or altered from any source distribution. -// - -#import - -#import "HockeySDKNullability.h" -NS_ASSUME_NONNULL_BEGIN - -@interface NSData (BITGZIP) - -- (nullable NSData *)bit_gzippedDataWithCompressionLevel:(float)level; -- (nullable NSData *)bit_gzippedData; -- (nullable NSData *)bit_gunzippedData; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/HockeySDK-iOS/Classes/BITHockeyAppClient.h b/submodules/HockeySDK-iOS/Classes/BITHockeyAppClient.h deleted file mode 100644 index 2cd677b10c..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITHockeyAppClient.h +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Author: Stephan Diederich - * - * Copyright (c) 2013-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import - -extern NSString * const kBITHockeyAppClientBoundary; - -/** - * Generic Hockey API client - */ -@interface BITHockeyAppClient : NSObject - -/** - * designated initializer - * - * @param baseURL the baseURL of the HockeyApp instance - */ -- (instancetype) initWithBaseURL:(NSURL*) baseURL; - -/** - * baseURL to which relative paths are appended - */ -@property (nonatomic, strong) NSURL *baseURL; - -/** - * creates an NRURLRequest for the given method and path by using - * the internally stored baseURL. - * - * @param method the HTTPMethod to check, must not be nil - * @param params parameters for the request (only supported for GET and POST for now) - * @param path path to append to baseURL. can be nil in which case "/" is appended - * - * @return an NSMutableURLRequest for further configuration - */ -- (NSMutableURLRequest *) requestWithMethod:(NSString*) method - path:(NSString *) path - parameters:(NSDictionary *) params; - -/** - * Access to the internal operation queue - */ -@property (nonatomic, strong) NSOperationQueue *operationQueue; - -#pragma mark - Helpers -/** - * create a post body from the given value, key and boundary. This is a convenience call to - * dataWithPostValue:forKey:contentType:boundary and aimed at NSString-content. - * - * @param value - - * @param key - - * @param boundary - - * - * @return NSData instance configured to be attached on a (post) URLRequest - */ -+ (NSData *)dataWithPostValue:(NSString *)value forKey:(NSString *)key boundary:(NSString *) boundary; - -/** - * create a post body from the given value, key and boundary and content type. - * - * @param value - - * @param key - - *@param contentType - - * @param boundary - - * @param filename - - * - * @return NSData instance configured to be attached on a (post) URLRequest - */ -+ (NSData *)dataWithPostValue:(NSData *)value forKey:(NSString *)key contentType:(NSString *)contentType boundary:(NSString *) boundary filename:(NSString *)filename; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITHockeyAppClient.m b/submodules/HockeySDK-iOS/Classes/BITHockeyAppClient.m deleted file mode 100644 index 6da7df4b60..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITHockeyAppClient.m +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Author: Stephan Diederich - * - * Copyright (c) 2013-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ -#import "BITHockeyAppClient.h" - -NSString * const kBITHockeyAppClientBoundary = @"----FOO"; - -@implementation BITHockeyAppClient - -- (instancetype)initWithBaseURL:(NSURL *)baseURL { - self = [super init]; - if ( self ) { - NSParameterAssert(baseURL); - _baseURL = baseURL; - } - return self; -} - -#pragma mark - Networking -- (NSMutableURLRequest *) requestWithMethod:(NSString*) method - path:(NSString *) path - parameters:(NSDictionary *)params { - NSParameterAssert(self.baseURL); - NSParameterAssert(method); - NSParameterAssert(params == nil || [method isEqualToString:@"POST"] || [method isEqualToString:@"GET"]); - path = path ? : @""; - - NSURL *endpoint = [self.baseURL URLByAppendingPathComponent:path]; - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:endpoint]; - request.HTTPMethod = method; - - if (params) { - if ([method isEqualToString:@"GET"]) { - NSString *absoluteURLString = [endpoint absoluteString]; - //either path already has parameters, or not - NSString *appenderFormat = [path rangeOfString:@"?"].location == NSNotFound ? @"?%@" : @"&%@"; - - endpoint = [NSURL URLWithString:[absoluteURLString stringByAppendingFormat:appenderFormat, - [self.class queryStringFromParameters:params withEncoding:NSUTF8StringEncoding]]]; - [request setURL:endpoint]; - } else { - //TODO: this is crap. Boundary must be the same as the one in appendData - //unify this! - NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", kBITHockeyAppClientBoundary]; - [request setValue:contentType forHTTPHeaderField:@"Content-type"]; - - NSMutableData *postBody = [NSMutableData data]; - [params enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL __unused *stop) { - [postBody appendData:[[self class] dataWithPostValue:value forKey:key boundary:kBITHockeyAppClientBoundary]]; - }]; - - [postBody appendData:(NSData *)[[NSString stringWithFormat:@"--%@--\r\n", kBITHockeyAppClientBoundary] dataUsingEncoding:NSUTF8StringEncoding]]; - - [request setHTTPBody:postBody]; - } - } - - return request; -} - -+ (NSData *)dataWithPostValue:(NSString *)value forKey:(NSString *)key boundary:(NSString *) boundary { - return [self dataWithPostValue:[value dataUsingEncoding:NSUTF8StringEncoding] forKey:key contentType:@"text" boundary:boundary filename:nil]; -} - -+ (NSData *)dataWithPostValue:(NSData *)value forKey:(NSString *)key contentType:(NSString *)contentType boundary:(NSString *) boundary filename:(NSString *)filename { - NSMutableData *postBody = [NSMutableData data]; - - [postBody appendData:(NSData *)[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; - - // There's certainly a better way to check if we are supposed to send binary data here. - if (filename){ - [postBody appendData:(NSData *)[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", key, filename] dataUsingEncoding:NSUTF8StringEncoding]]; - [postBody appendData:(NSData *)[[NSString stringWithFormat:@"Content-Type: %@\r\n", contentType] dataUsingEncoding:NSUTF8StringEncoding]]; - [postBody appendData:(NSData *)[[NSString stringWithFormat:@"Content-Transfer-Encoding: binary\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]]; - } else { - [postBody appendData:(NSData *)[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n", key] dataUsingEncoding:NSUTF8StringEncoding]]; - [postBody appendData:(NSData *)[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", contentType] dataUsingEncoding:NSUTF8StringEncoding]]; - } - - [postBody appendData:value]; - [postBody appendData:(NSData *)[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; - - return postBody; -} - - -+ (NSString *) queryStringFromParameters:(NSDictionary *) params withEncoding:(NSStringEncoding) __unused encoding { - NSMutableString *queryString = [NSMutableString new]; - [params enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSString* value, BOOL __unused *stop) { - NSAssert([key isKindOfClass:[NSString class]], @"Query parameters can only be string-string pairs"); - NSAssert([value isKindOfClass:[NSString class]], @"Query parameters can only be string-string pairs"); - - [queryString appendFormat:queryString.length ? @"&%@=%@" : @"%@=%@", key, value]; - }]; - return queryString; -} - -- (NSOperationQueue *)operationQueue { - if(nil == _operationQueue) { - _operationQueue = [[NSOperationQueue alloc] init]; - _operationQueue.maxConcurrentOperationCount = 1; - } - return _operationQueue; -} - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITHockeyAttachment.h b/submodules/HockeySDK-iOS/Classes/BITHockeyAttachment.h deleted file mode 100644 index ec6cd02df4..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITHockeyAttachment.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import - -/** - Provides support to add binary attachments to crash reports and feedback messages - - This is used by `[BITCrashManagerDelegate attachmentForCrashManager:]`, - `[BITFeedbackComposeViewController prepareWithItems:]` and - `[BITFeedbackManager showFeedbackComposeViewWithPreparedItems:]` - */ -@interface BITHockeyAttachment : NSObject - -/** - The filename the attachment should get - */ -@property (nonatomic, readonly, copy) NSString *filename; - -/** - The attachment data as NSData object - */ -@property (nonatomic, readonly, strong) NSData *hockeyAttachmentData; - -/** - The content type of your data as MIME type - */ -@property (nonatomic, readonly, copy) NSString *contentType; - -/** - Create an BITHockeyAttachment instance with a given filename and NSData object - - @param filename The filename the attachment should get. If nil will get a automatically generated filename - @param hockeyAttachmentData The attachment data as NSData. The instance will be ignore if this is set to nil! - @param contentType The content type of your data as MIME type. If nil will be set to "application/octet-stream" - - @return An instance of BITHockeyAttachment. - */ -- (instancetype)initWithFilename:(NSString *)filename - hockeyAttachmentData:(NSData *)hockeyAttachmentData - contentType:(NSString *)contentType; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITHockeyAttachment.m b/submodules/HockeySDK-iOS/Classes/BITHockeyAttachment.m deleted file mode 100644 index 283f267785..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITHockeyAttachment.m +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_CRASH_REPORTER || HOCKEYSDK_FEATURE_FEEDBACK - -#import "BITHockeyAttachment.h" - -@implementation BITHockeyAttachment - -- (instancetype)initWithFilename:(NSString *)filename - hockeyAttachmentData:(NSData *)hockeyAttachmentData - contentType:(NSString *)contentType -{ - if ((self = [super init])) { - _filename = filename; - - _hockeyAttachmentData = hockeyAttachmentData; - - if (contentType) { - _contentType = contentType; - } else { - _contentType = @"application/octet-stream"; - } - - } - - return self; -} - - -#pragma mark - NSCoder - -- (void)encodeWithCoder:(NSCoder *)encoder { - if (self.filename) { - [encoder encodeObject:self.filename forKey:@"filename"]; - } - if (self.hockeyAttachmentData) { - [encoder encodeObject:self.hockeyAttachmentData forKey:@"data"]; - } - [encoder encodeObject:self.contentType forKey:@"contentType"]; -} - -- (instancetype)initWithCoder:(NSCoder *)decoder { - if ((self = [super init])) { - _filename = [decoder decodeObjectForKey:@"filename"]; - _hockeyAttachmentData = [decoder decodeObjectForKey:@"data"]; - _contentType = [decoder decodeObjectForKey:@"contentType"]; - } - return self; -} - -@end - -#endif /* HOCKEYSDK_FEATURE_CRASH_REPORTER || HOCKEYSDK_FEATURE_FEEDBACK */ diff --git a/submodules/HockeySDK-iOS/Classes/BITHockeyBaseManager.h b/submodules/HockeySDK-iOS/Classes/BITHockeyBaseManager.h deleted file mode 100644 index 20da0b8c18..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITHockeyBaseManager.h +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import -#import - - -/** - The internal superclass for all component managers - - */ - -@interface BITHockeyBaseManager : NSObject - -///----------------------------------------------------------------------------- -/// @name Modules -///----------------------------------------------------------------------------- - - -/** - Defines the server URL to send data to or request data from - - By default this is set to the HockeyApp servers and there rarely should be a - need to modify that. - */ -@property (nonatomic, copy) NSString *serverURL; - - -///----------------------------------------------------------------------------- -/// @name User Interface -///----------------------------------------------------------------------------- - -/** - The UIBarStyle of the update user interface navigation bar. - - Default is UIBarStyleBlackOpaque - @see navigationBarTintColor - */ -@property (nonatomic, assign) UIBarStyle barStyle; - -/** - The navigationbar tint color of the update user interface navigation bar. - - The navigationBarTintColor is used by default, you can either overwrite it `navigationBarTintColor` - or define another `barStyle` instead. - - Default is RGB(25, 25, 25) - @see barStyle - */ -@property (nonatomic, strong) UIColor *navigationBarTintColor; - -/** - The UIModalPresentationStyle for showing the update user interface when invoked - with the update alert. - */ -@property (nonatomic, assign) UIModalPresentationStyle modalPresentationStyle; - -+ (void)setPresentAlert:(void (^)(UIAlertController *))presentAlert; -+ (void)setPresentView:(void (^)(UIViewController *))presentView; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITHockeyBaseManager.m b/submodules/HockeySDK-iOS/Classes/BITHockeyBaseManager.m deleted file mode 100644 index 03b70b80cf..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITHockeyBaseManager.m +++ /dev/null @@ -1,372 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "HockeySDK.h" -#import "HockeySDKPrivate.h" - -#import "BITHockeyHelper.h" - -#import "BITHockeyBaseManager.h" -#import "BITHockeyBaseManagerPrivate.h" -#if HOCKEYSDK_FEATURE_AUTHENTICATOR || HOCKEYSDK_FEATURE_UPDATES || HOCKEYSDK_FEATURE_FEEDBACK -#import "BITHockeyBaseViewController.h" -#endif - -#import "BITKeychainUtils.h" - -#import -#import -#import - -// We need BIT_UNUSED macro to make sure there aren't any warnings when building -// HockeySDK Distribution scheme. Since several configurations are build in this scheme -// and different features can be turned on and off we can't just use __unused attribute. -#if !defined (HOCKEYSDK_CONFIGURATION_ReleaseCrashOnlyExtensions) -#if HOCKEYSDK_FEATURE_AUTHENTICATOR || HOCKEYSDK_FEATURE_UPDATES || HOCKEYSDK_FEATURE_FEEDBACK -#define BIT_UNUSED -#else -#define BIT_UNUSED __unused -#endif -#endif - -@interface BITHockeyBaseManager () - -@property (nonatomic, strong) UINavigationController *navController; -@property (nonatomic, strong) NSDateFormatter *rfc3339Formatter; - -@end - -static void (^_presentAlert)(UIAlertController *) = nil; -static void (^_presentView)(UIViewController *) = nil; - -@implementation BITHockeyBaseManager - -- (instancetype)init { - if ((self = [super init])) { - _serverURL = BITHOCKEYSDK_URL; - _barStyle = UIBarStyleDefault; - _modalPresentationStyle = UIModalPresentationFormSheet; - - NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; - _rfc3339Formatter = [[NSDateFormatter alloc] init]; - [_rfc3339Formatter setLocale:enUSPOSIXLocale]; - [_rfc3339Formatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"]; - [_rfc3339Formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; - } - return self; -} - -- (instancetype)initWithAppIdentifier:(NSString *)appIdentifier appEnvironment:(BITEnvironment)environment { - if ((self = [self init])) { - _appIdentifier = appIdentifier; - _appEnvironment = environment; - } - return self; -} - - -#pragma mark - Private - -- (void)reportError:(NSError *)error { - BITHockeyLogError(@"ERROR: %@", [error localizedDescription]); -} - -- (NSString *)encodedAppIdentifier { - return bit_encodeAppIdentifier(self.appIdentifier); -} - -- (NSString *)getDevicePlatform { - size_t size; - sysctlbyname("hw.machine", NULL, &size, NULL, 0); - char *answer = (char*)malloc(size); - if (answer == NULL) - return @""; - sysctlbyname("hw.machine", answer, &size, NULL, 0); - NSString *platform = [NSString stringWithCString:answer encoding: NSUTF8StringEncoding]; - free(answer); - return platform; -} - -- (NSString *)executableUUID { - const struct mach_header *executableHeader = NULL; - for (uint32_t i = 0; i < _dyld_image_count(); i++) { - const struct mach_header *header = _dyld_get_image_header(i); - if (header->filetype == MH_EXECUTE) { - executableHeader = header; - break; - } - } - - if (!executableHeader) - return @""; - - BOOL is64bit = executableHeader->magic == MH_MAGIC_64 || executableHeader->magic == MH_CIGAM_64; - uintptr_t cursor = (uintptr_t)executableHeader + (is64bit ? sizeof(struct mach_header_64) : sizeof(struct mach_header)); - const struct segment_command *segmentCommand = NULL; - for (uint32_t i = 0; i < executableHeader->ncmds; i++, cursor += segmentCommand->cmdsize) { - segmentCommand = (struct segment_command *)cursor; - if (segmentCommand->cmd == LC_UUID) { - const struct uuid_command *uuidCommand = (const struct uuid_command *)segmentCommand; - const uint8_t *uuid = uuidCommand->uuid; - return [[NSString stringWithFormat:@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", - uuid[0], uuid[1], uuid[2], uuid[3], - uuid[4], uuid[5], uuid[6], uuid[7], - uuid[8], uuid[9], uuid[10], uuid[11], - uuid[12], uuid[13], uuid[14], uuid[15]] - lowercaseString]; - } - } - - return @""; -} - -#if !defined (HOCKEYSDK_CONFIGURATION_ReleaseCrashOnlyExtensions) -- (UIWindow *)findVisibleWindow { - UIWindow *visibleWindow = [UIApplication sharedApplication].keyWindow; - - if (!(visibleWindow.hidden)) { - return visibleWindow; - } - - // if the rootViewController property (available >= iOS 4.0) of the main window is set, we present the modal view controller on top of the rootViewController - NSArray *windows = [[UIApplication sharedApplication] windows]; - for (UIWindow *window in windows) { - if (!window.hidden && !visibleWindow) { - visibleWindow = window; - } - if ([UIWindow instancesRespondToSelector:@selector(rootViewController)]) { - if (!(window.hidden) && ([window rootViewController])) { - visibleWindow = window; - BITHockeyLogDebug(@"INFO: UIWindow with rootViewController found: %@", visibleWindow); - break; - } - } - } - - return visibleWindow; -} - -/** - * Provide a custom UINavigationController with customized appearance settings - * - * @param viewController The root viewController - * @param modalPresentationStyle The modal presentation style - * - * @return A UINavigationController - */ -- (UINavigationController *)customNavigationControllerWithRootViewController:(UIViewController *)viewController presentationStyle:(UIModalPresentationStyle)modalPresentationStyle { - UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController]; - navController.navigationBar.barStyle = self.barStyle; - if (self.navigationBarTintColor) { - navController.navigationBar.tintColor = self.navigationBarTintColor; - } else { - // in case of iOS 7 we overwrite the tint color on the navigation bar - if ([UIWindow instancesRespondToSelector:NSSelectorFromString(@"tintColor")]) { - [navController.navigationBar setTintColor:BIT_RGBCOLOR(0, 122, 255)]; - } - } - navController.modalPresentationStyle = modalPresentationStyle; - - return navController; -} - -- (UIViewController *)visibleWindowRootViewController { - UIViewController *parentViewController = nil; - id strongDelegate = [BITHockeyManager sharedHockeyManager].delegate; - if ([strongDelegate respondsToSelector:@selector(viewControllerForHockeyManager:componentManager:)]) { - parentViewController = [strongDelegate viewControllerForHockeyManager:[BITHockeyManager sharedHockeyManager] componentManager:self]; - } - - UIWindow *visibleWindow = [self findVisibleWindow]; - - if (parentViewController == nil) { - parentViewController = [visibleWindow rootViewController]; - } - - // use topmost modal view - while (parentViewController.presentedViewController) { - parentViewController = parentViewController.presentedViewController; - } - - // special addition to get rootViewController from three20 which has it's own controller handling - if (NSClassFromString(@"TTNavigator")) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" - UIViewController *ttParentViewController = nil; - ttParentViewController = [[NSClassFromString(@"TTNavigator") performSelector:(NSSelectorFromString(@"navigator"))] visibleViewController]; - if (ttParentViewController) - parentViewController = ttParentViewController; -#pragma clang diagnostic pop - } - - return parentViewController; -} - -- (void)showAlertController:(UIViewController *)alertController { - - // always execute this on the main thread - dispatch_async(dispatch_get_main_queue(), ^{ - if (_presentAlert) { - _presentAlert(alertController); - } else { - UIViewController *parentViewController = [self visibleWindowRootViewController]; - - // as per documentation this only works if called from within viewWillAppear: or viewDidAppear: - // in tests this also worked fine on iOS 6 and 7 but not on iOS 5 so we are still trying this - if ([parentViewController isKindOfClass:NSClassFromString(@"UIAlertController")] || [parentViewController isBeingPresented]) { - BITHockeyLogWarning(@"WARNING: There is already a view controller being presented onto the parentViewController. Delaying presenting the new view controller by 0.5s."); - [self performSelector:@selector(showAlertController:) withObject:alertController afterDelay:0.5]; - return; - } - - if (parentViewController) { - [parentViewController presentViewController:alertController animated:YES completion:nil]; - } - } - }); -} - -- (void)showView:(UIViewController *)viewController { - if (_presentView) { - _presentView(viewController); - return; - } - - // if we compile Crash only, then BITHockeyBaseViewController is not included - // in the headers and will cause a warning with the modulemap file -#if HOCKEYSDK_FEATURE_AUTHENTICATOR || HOCKEYSDK_FEATURE_UPDATES || HOCKEYSDK_FEATURE_FEEDBACK - UIViewController *parentViewController = [self visibleWindowRootViewController]; - - // as per documentation this only works if called from within viewWillAppear: or viewDidAppear: - // in tests this also worked fine on iOS 6 and 7 but not on iOS 5 so we are still trying this - if ([parentViewController isBeingPresented]) { - BITHockeyLogDebug(@"INFO: There is already a view controller being presented onto the parentViewController. Delaying presenting the new view controller by 0.5s."); - [self performSelector:@selector(showView:) withObject:viewController afterDelay:0.5]; - return; - } - - if (self.navController != nil) self.navController = nil; - - self.navController = [self customNavigationControllerWithRootViewController:viewController presentationStyle:self.modalPresentationStyle]; - - if (parentViewController) { - self.navController.modalTransitionStyle = UIModalTransitionStyleCoverVertical; - - // page sheet for the iPad - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { - self.navController.modalPresentationStyle = UIModalPresentationFormSheet; - } - - if ([viewController isKindOfClass:[BITHockeyBaseViewController class]]) - [(BITHockeyBaseViewController *)viewController setModalAnimated:YES]; - - [parentViewController presentViewController:self.navController animated:YES completion:nil]; - } else { - // if not, we add a subview to the window. A bit hacky but should work in most circumstances. - // Also, we don't get a nice animation for free, but hey, this is for beta not production users ;) - UIWindow *visibleWindow = [self findVisibleWindow]; - - BITHockeyLogDebug(@"INFO: No rootViewController found, using UIWindow-approach: %@", visibleWindow); - if ([viewController isKindOfClass:[BITHockeyBaseViewController class]]) - [(BITHockeyBaseViewController *)viewController setModalAnimated:NO]; - [visibleWindow addSubview:self.navController.view]; - } -#endif /* HOCKEYSDK_FEATURE_AUTHENTICATOR || HOCKEYSDK_FEATURE_UPDATES || HOCKEYSDK_FEATURE_FEEDBACK */ -} -#endif // HOCKEYSDK_CONFIGURATION_ReleaseCrashOnlyExtensions && HOCKEYSDK_CONFIGURATION_RelaseCrashOnlyWatchOS - - -- (BOOL)addStringValueToKeychain:(NSString *)stringValue forKey:(NSString *)key { - if (!key || !stringValue) - return NO; - - NSError *error = nil; - return [BITKeychainUtils storeUsername:key - andPassword:stringValue - forServiceName:bit_keychainHockeySDKServiceName() - updateExisting:YES - error:&error]; -} - -- (BOOL)addStringValueToKeychainForThisDeviceOnly:(NSString *)stringValue forKey:(NSString *)key { - if (!key || !stringValue) - return NO; - - NSError *error = nil; - return [BITKeychainUtils storeUsername:key - andPassword:stringValue - forServiceName:bit_keychainHockeySDKServiceName() - updateExisting:YES - accessibility:kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly - error:&error]; -} - -- (NSString *)stringValueFromKeychainForKey:(NSString *)key { - if (!key) - return nil; - - NSError *error = nil; - return [BITKeychainUtils getPasswordForUsername:key - andServiceName:bit_keychainHockeySDKServiceName() - error:&error]; -} - -- (BOOL)removeKeyFromKeychain:(NSString *)key { - NSError *error = nil; - return [BITKeychainUtils deleteItemForUsername:key - andServiceName:bit_keychainHockeySDKServiceName() - error:&error]; -} - - -#pragma mark - Manager Control - -- (void)startManager { -} - -#pragma mark - Helpers - -- (NSDate *)parseRFC3339Date:(NSString *)dateString { - NSDate *date = nil; - NSError *error = nil; - if (![self.rfc3339Formatter getObjectValue:&date forString:dateString range:nil error:&error]) { - BITHockeyLogWarning(@"WARNING: Invalid date '%@' string: %@", dateString, error); - } - - return date; -} - -+ (void)setPresentAlert:(void (^)(UIAlertController *))presentAlert { - _presentAlert = [presentAlert copy]; -} - -+ (void)setPresentView:(void (^)(UIViewController *))presentView { - _presentView = [presentView copy]; -} - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITHockeyBaseManagerPrivate.h b/submodules/HockeySDK-iOS/Classes/BITHockeyBaseManagerPrivate.h deleted file mode 100644 index 2ddf12d92e..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITHockeyBaseManagerPrivate.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import -#import -#import "BITHockeyManager.h" - -@class BITHockeyBaseManager; -@class BITHockeyBaseViewController; - -@interface BITHockeyBaseManager() - -@property (nonatomic, copy) NSString *appIdentifier; - -@property (nonatomic, assign, readonly) BITEnvironment appEnvironment; - -- (instancetype)initWithAppIdentifier:(NSString *)appIdentifier appEnvironment:(BITEnvironment)environment; - -- (void)startManager; - -/** - * by default, just logs the message - * - * can be overridden by subclasses to do their own error handling, - * e.g. to show UI - * - * @param error NSError - */ -- (void)reportError:(NSError *)error; - -/** url encoded version of the appIdentifier - - where appIdentifier is either the value this object was initialized with, - or the main bundles CFBundleIdentifier if appIdentifier is nil - */ -- (NSString *)encodedAppIdentifier; - -// device / application helpers -- (NSString *)getDevicePlatform; -- (NSString *)executableUUID; - -#if !defined (HOCKEYSDK_CONFIGURATION_ReleaseCrashOnlyExtensions) -// UI helpers -- (UIWindow *)findVisibleWindow; -- (UINavigationController *)customNavigationControllerWithRootViewController:(UIViewController *)viewController presentationStyle:(UIModalPresentationStyle)presentationStyle; - -/** - * Present an UIAlertController on the visible root UIViewController. - * - * Uses `visibleWindowRootViewController` to find a controller on which to present the UIAlertController on. - * This method is always dispatched on the main queue. - * - * @param alertController The UIAlertController to be presented. - */ -- (void)showAlertController:(UIViewController *)alertController; - -- (void)showView:(UIViewController *)viewController; -#endif - -// Date helpers -- (NSDate *)parseRFC3339Date:(NSString *)dateString; - -// keychain helpers -- (BOOL)addStringValueToKeychain:(NSString *)stringValue forKey:(NSString *)key; -- (BOOL)addStringValueToKeychainForThisDeviceOnly:(NSString *)stringValue forKey:(NSString *)key; -- (NSString *)stringValueFromKeychainForKey:(NSString *)key; -- (BOOL)removeKeyFromKeychain:(NSString *)key; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITHockeyBaseViewController.h b/submodules/HockeySDK-iOS/Classes/BITHockeyBaseViewController.h deleted file mode 100644 index d7701036be..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITHockeyBaseViewController.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import - -@interface BITHockeyBaseViewController : UITableViewController - -@property (nonatomic, readwrite) BOOL modalAnimated; - -- (instancetype)initWithModalStyle:(BOOL)modal; -- (instancetype)initWithStyle:(UITableViewStyle)style modal:(BOOL)modal; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITHockeyBaseViewController.m b/submodules/HockeySDK-iOS/Classes/BITHockeyBaseViewController.m deleted file mode 100644 index 1632c342ce..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITHockeyBaseViewController.m +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_AUTHENTICATOR || HOCKEYSDK_FEATURE_UPDATES || HOCKEYSDK_FEATURE_FEEDBACK - -#import "BITHockeyBaseViewController.h" -#import "HockeySDKPrivate.h" - -@interface BITHockeyBaseViewController () - -@property (nonatomic) BOOL modal; - -@end - -@implementation BITHockeyBaseViewController - - -- (instancetype)initWithStyle:(UITableViewStyle)style { - self = [super initWithStyle:style]; - if (self) { - _modalAnimated = YES; - _modal = NO; - } - return self; -} - -- (instancetype)initWithStyle:(UITableViewStyle)style modal:(BOOL)modal { - self = [self initWithStyle:style]; - if (self) { - _modal = modal; - - //might be better in viewDidLoad, but to workaround rdar://12214613 and as it doesn't - //hurt, we do it here - if (_modal) { - self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone - target:self - action:@selector(onDismissModal:)]; - } - } - return self; -} - -- (instancetype)initWithModalStyle:(BOOL)modal { - self = [self initWithStyle:UITableViewStylePlain modal:modal]; - return self; -} - - -#pragma mark - View lifecycle - -- (void)onDismissModal:(id) __unused sender { - if (self.modal) { - UIViewController *presentingViewController = [self presentingViewController]; - - // If there is no presenting view controller just remove view - if (presentingViewController && self.modalAnimated) { - [presentingViewController dismissViewControllerAnimated:YES completion:nil]; - } else { - [self.navigationController.view removeFromSuperview]; - } - } else { - [self.navigationController popViewControllerAnimated:YES]; - } -} - - -#pragma mark - Rotation - --(UIInterfaceOrientationMask)supportedInterfaceOrientations { - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { - return (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscape); - } else { - return UIInterfaceOrientationMaskAll; - } -} - -#pragma mark - Modal presentation - - -@end - -#endif /* HOCKEYSDK_FEATURE_AUTHENTICATOR || HOCKEYSDK_FEATURE_UPDATES || HOCKEYSDK_FEATURE_FEEDBACK */ diff --git a/submodules/HockeySDK-iOS/Classes/BITHockeyHelper+Application.h b/submodules/HockeySDK-iOS/Classes/BITHockeyHelper+Application.h deleted file mode 100644 index e7397ba63a..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITHockeyHelper+Application.h +++ /dev/null @@ -1,48 +0,0 @@ -#import -#import - -#import "BITHockeyHelper.h" -/* - * Workaround for exporting symbols from category object files. - */ -extern NSString *BITHockeyHelperApplicationCategory; - -/** - * App states - */ -typedef NS_ENUM(NSInteger, BITApplicationState) { - - /** - * Application is active. - */ - BITApplicationStateActive = UIApplicationStateActive, - - /** - * Application is inactive. - */ - BITApplicationStateInactive = UIApplicationStateInactive, - - /** - * Application is in background. - */ - BITApplicationStateBackground = UIApplicationStateBackground, - - /** - * Application state can't be determined. - */ - BITApplicationStateUnknown -}; - -@interface BITHockeyHelper (Application) - -/** - * Get current application state. - * - * @return Current state of the application or BITApplicationStateUnknown while the state can't be determined. - * - * @discussion The application state may not be available everywhere. Application extensions doesn't have it for instance, - * in that case the BITApplicationStateUnknown value is returned. - */ -+ (BITApplicationState)applicationState; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITHockeyHelper+Application.m b/submodules/HockeySDK-iOS/Classes/BITHockeyHelper+Application.m deleted file mode 100644 index 1a31bc2e9d..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITHockeyHelper+Application.m +++ /dev/null @@ -1,43 +0,0 @@ -#import "BITHockeyHelper+Application.h" - -/* - * Workaround for exporting symbols from category object files. - */ -NSString *BITHockeyHelperApplicationCategory; - -@implementation BITHockeyHelper (Application) - -+ (BITApplicationState)applicationState { - - // App extensions must not access sharedApplication. - if (!bit_isRunningInAppExtension()) { - - __block BITApplicationState state; - dispatch_block_t block = ^{ - state = (BITApplicationState)[[self class] sharedAppState]; - }; - - if ([NSThread isMainThread]) { - block(); - } else { - dispatch_sync(dispatch_get_main_queue(), block); - } - - return state; - } - return BITApplicationStateUnknown; -} - -+ (UIApplication *)sharedApplication { - - // Compute selector at runtime for more discretion. - SEL sharedAppSel = NSSelectorFromString(@"sharedApplication"); - return ((UIApplication * (*)(id, SEL))[[UIApplication class] methodForSelector:sharedAppSel])([UIApplication class], - sharedAppSel); -} - -+ (UIApplicationState)sharedAppState { - return [[[[self class] sharedApplication] valueForKey:@"applicationState"] longValue]; -} - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITHockeyHelper.h b/submodules/HockeySDK-iOS/Classes/BITHockeyHelper.h deleted file mode 100644 index e25f0d4a76..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITHockeyHelper.h +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import -#import -#import "HockeySDKEnums.h" - -@interface BITHockeyHelper : NSObject - -FOUNDATION_EXPORT NSString *const kBITExcludeApplicationSupportFromBackup; - -+ (BOOL)isURLSessionSupported; - -/* - * Checks if the privacy description for iOS 10+ has been set in info plist. - * @return YES for < iOS 10. YES/NO in iOS 10+ if NSPhotoLibraryUsageDescription is present in the app's Info.plist. - */ -+ (BOOL)isPhotoAccessPossible; - -@end - -NSString *bit_settingsDir(void); - -BOOL bit_validateEmail(NSString *email); -NSString *bit_keychainHockeySDKServiceName(void); - -/* Fix bug where Application Support was excluded from backup. */ -void bit_fixBackupAttributeForURL(NSURL *directoryURL); - -NSComparisonResult bit_versionCompare(NSString *stringA, NSString *stringB); -NSString *bit_mainBundleIdentifier(void); -NSString *bit_encodeAppIdentifier(NSString *inputString); -NSString *bit_appIdentifierToGuid(NSString *appIdentifier); -NSString *bit_appName(NSString *placeHolderString); -NSString *bit_UUID(void); -NSString *bit_appAnonID(BOOL forceNewAnonID); -BOOL bit_isPreiOS10Environment(void); -BOOL bit_isAppStoreReceiptSandbox(void); -BOOL bit_hasEmbeddedMobileProvision(void); -BITEnvironment bit_currentAppEnvironment(void); -BOOL bit_isRunningInAppExtension(void); - -/** - * Check if the debugger is attached - * - * Taken from https://github.com/plausiblelabs/plcrashreporter/blob/2dd862ce049e6f43feb355308dfc710f3af54c4d/Source/Crash%20Demo/main.m#L96 - * - * @return `YES` if the debugger is attached to the current process, `NO` otherwise - */ -BOOL bit_isDebuggerAttached(void); - -/* NSString helpers */ -NSString *bit_URLEncodedString(NSString *inputString); - -/* Context helpers */ -NSString *bit_utcDateString(NSDate *date); -NSString *bit_devicePlatform(void); -NSString *bit_devicePlatform(void); -NSString *bit_deviceType(void); -NSString *bit_osVersionBuild(void); -NSString *bit_osName(void); -NSString *bit_deviceLocale(void); -NSString *bit_deviceLanguage(void); -NSString *bit_screenSize(void); -NSString *bit_sdkVersion(void); -NSString *bit_appVersion(void); - -#if !defined (HOCKEYSDK_CONFIGURATION_ReleaseCrashOnly) && !defined (HOCKEYSDK_CONFIGURATION_ReleaseCrashOnlyExtensions) -/* AppIcon helper */ -NSString *bit_validAppIconStringFromIcons(NSBundle *resourceBundle, NSArray *icons); -NSString *bit_validAppIconFilename(NSBundle *bundle, NSBundle *resourceBundle); - -/* UIImage helpers */ -UIImage *bit_roundedCornerImage(UIImage *inputImage, CGFloat cornerSize, NSInteger borderSize); -UIImage *bit_imageToFitSize(UIImage *inputImage, CGSize fitSize, BOOL honorScaleFactor); -UIImage *bit_reflectedImageWithHeight(UIImage *inputImage, NSUInteger height, CGFloat fromAlpha, CGFloat toAlpha); - -UIImage *bit_newWithContentsOfResolutionIndependentFile(NSString * path); -UIImage *bit_imageWithContentsOfResolutionIndependentFile(NSString * path); -UIImage *bit_imageNamed(NSString *imageName, NSString *bundleName); -UIImage *bit_screenshot(void); -UIImage *bit_appIcon(void); - -#endif diff --git a/submodules/HockeySDK-iOS/Classes/BITHockeyHelper.m b/submodules/HockeySDK-iOS/Classes/BITHockeyHelper.m deleted file mode 100644 index f9b9052904..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITHockeyHelper.m +++ /dev/null @@ -1,1038 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - - -#import "BITHockeyHelper+Application.h" -#import "BITKeychainUtils.h" -#import "HockeySDK.h" -#import "HockeySDKPrivate.h" -#if !defined (HOCKEYSDK_CONFIGURATION_ReleaseCrashOnly) && !defined (HOCKEYSDK_CONFIGURATION_ReleaseCrashOnlyExtensions) -#import -#endif - -#import -#import - -static NSString *const kBITUtcDateFormatter = @"utcDateFormatter"; -NSString *const kBITExcludeApplicationSupportFromBackup = @"kBITExcludeApplicationSupportFromBackup"; - -@implementation BITHockeyHelper - -/** - * @discussion - * Workaround for exporting symbols from category object files. - * See article https://medium.com/ios-os-x-development/categories-in-static-libraries-78e41f8ddb96#.aedfl1kl0 - */ -__attribute__((used)) static void importCategories() { - [NSString stringWithFormat:@"%@", BITHockeyHelperApplicationCategory]; -} - -+ (BOOL)isURLSessionSupported { - id nsurlsessionClass = NSClassFromString(@"NSURLSessionUploadTask"); - BOOL isUrlSessionSupported = (nsurlsessionClass && !bit_isRunningInAppExtension()); - return isUrlSessionSupported; -} - -+ (BOOL)isPhotoAccessPossible { - if(bit_isPreiOS10Environment()) { - return YES; - } - else { - NSString *privacyDescription = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSPhotoLibraryUsageDescription"]; - BOOL privacyStringSet = (privacyDescription != nil) && (privacyDescription.length > 0); - - return privacyStringSet; - } -} - -@end - -typedef struct { - uint8_t info_version; - const char bit_version[16]; - const char bit_build[16]; -} bit_info_t; - - -#pragma mark - Helpers - -NSString *bit_settingsDir(void) { - static NSString *settingsDir = nil; - static dispatch_once_t predSettingsDir; - - dispatch_once(&predSettingsDir, ^{ - NSFileManager *fileManager = [[NSFileManager alloc] init]; - - // temporary directory for crashes grabbed from PLCrashReporter - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); - settingsDir = [[paths objectAtIndex:0] stringByAppendingPathComponent:BITHOCKEY_IDENTIFIER]; - - if (![fileManager fileExistsAtPath:settingsDir]) { - NSDictionary *attributes = [NSDictionary dictionaryWithObject: [NSNumber numberWithUnsignedLong: 0755] forKey: NSFilePosixPermissions]; - NSError *theError = NULL; - - [fileManager createDirectoryAtPath:settingsDir withIntermediateDirectories: YES attributes: attributes error: &theError]; - } - }); - - return settingsDir; -} - -BOOL bit_validateEmail(NSString *email) { - NSString *emailRegex = - @"(?:[a-z0-9!#$%\\&'*+/=?\\^_`{|}~-]+(?:\\.[a-z0-9!#$%\\&'*+/=?\\^_`{|}" - @"~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\" - @"x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-" - @"z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5" - @"]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-" - @"9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21" - @"-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])"; - NSPredicate *emailTest = [NSPredicate predicateWithFormat:@"SELF MATCHES[c] %@", emailRegex]; - - return [emailTest evaluateWithObject:email]; -} - -NSString *bit_keychainHockeySDKServiceName(void) { - static NSString *serviceName = nil; - static dispatch_once_t predServiceName; - - dispatch_once(&predServiceName, ^{ - serviceName = [NSString stringWithFormat:@"%@.HockeySDK", bit_mainBundleIdentifier()]; - }); - - return serviceName; -} - -NSComparisonResult bit_versionCompare(NSString *stringA, NSString *stringB) { - // Extract plain version number from self - NSString *plainSelf = stringA; - NSRange letterRange = [plainSelf rangeOfCharacterFromSet: [NSCharacterSet letterCharacterSet]]; - if (letterRange.length) - plainSelf = [plainSelf substringToIndex: letterRange.location]; - - // Extract plain version number from other - NSString *plainOther = stringB; - letterRange = [plainOther rangeOfCharacterFromSet: [NSCharacterSet letterCharacterSet]]; - if (letterRange.length) - plainOther = [plainOther substringToIndex: letterRange.location]; - - // Compare plain versions - NSComparisonResult result = [plainSelf compare:plainOther options:NSNumericSearch]; - - // If plain versions are equal, compare full versions - if (result == NSOrderedSame) - result = [stringA compare:stringB options:NSNumericSearch]; - - // Done - return result; -} - -#pragma mark Exclude from backup fix - -void bit_fixBackupAttributeForURL(NSURL *directoryURL) { - - BOOL shouldExcludeAppSupportDirFromBackup = [[NSUserDefaults standardUserDefaults] boolForKey:kBITExcludeApplicationSupportFromBackup]; - if (shouldExcludeAppSupportDirFromBackup) { - return; - } - - if (directoryURL) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSError *getResourceError = nil; - NSNumber *appSupportDirExcludedValue; - if ([directoryURL getResourceValue:&appSupportDirExcludedValue forKey:NSURLIsExcludedFromBackupKey error:&getResourceError] && appSupportDirExcludedValue) { - NSError *setResourceError = nil; - if(![directoryURL setResourceValue:@NO forKey:NSURLIsExcludedFromBackupKey error:&setResourceError]) { - BITHockeyLogError(@"ERROR: Error while setting resource value: %@", setResourceError.localizedDescription); - } - } else { - BITHockeyLogError(@"ERROR: Error while retrieving resource value: %@", getResourceError.localizedDescription); - } - }); - } -} - -#pragma mark Identifiers - -NSString *bit_mainBundleIdentifier(void) { - return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"]; -} - -NSString *bit_encodeAppIdentifier(NSString *inputString) { - return (inputString ? bit_URLEncodedString(inputString) : bit_URLEncodedString(bit_mainBundleIdentifier())); -} - -NSString *bit_appIdentifierToGuid(NSString *appIdentifier) { - NSMutableString *guid; - NSString *cleanAppId = [appIdentifier stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - if(cleanAppId && cleanAppId.length == 32) { - // Insert dashes so that DC will accept th appidentifier (as a replacement for iKey) - guid = [NSMutableString stringWithString:cleanAppId]; - [guid insertString:@"-" atIndex:20]; - [guid insertString:@"-" atIndex:16]; - [guid insertString:@"-" atIndex:12]; - [guid insertString:@"-" atIndex:8]; - } - return [guid copy]; -} - -NSString *bit_appName(NSString *placeHolderString) { - NSString *appName = [[[NSBundle mainBundle] localizedInfoDictionary] objectForKey:@"CFBundleDisplayName"]; - if (!appName) - appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]; - if (!appName) - appName = [[[NSBundle mainBundle] localizedInfoDictionary] objectForKey:@"CFBundleName"]; - if (!appName) - appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"] ?: placeHolderString; - - return appName; -} - -NSString *bit_UUID(void) { - return [[NSUUID UUID] UUIDString]; -} - -NSString *bit_appAnonID(BOOL forceNewAnonID) { - static NSString *appAnonID = nil; - static dispatch_once_t predAppAnonID; - __block NSError *error = nil; - NSString *appAnonIDKey = @"appAnonID"; - - if (forceNewAnonID) { - appAnonID = bit_UUID(); - // store this UUID in the keychain (on this device only) so we can be sure to always have the same ID upon app startups - if (appAnonID) { - // add to keychain in a background thread, since we got reports that storing to the keychain may take several seconds sometimes and cause the app to be killed - // and we don't care about the result anyway - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ - [BITKeychainUtils storeUsername:appAnonIDKey - andPassword:appAnonID - forServiceName:bit_keychainHockeySDKServiceName() - updateExisting:YES - accessibility:kSecAttrAccessibleAlwaysThisDeviceOnly - error:&error]; - }); - } - } else { - dispatch_once(&predAppAnonID, ^{ - // first check if we already have an install string in the keychain - appAnonID = [BITKeychainUtils getPasswordForUsername:appAnonIDKey andServiceName:bit_keychainHockeySDKServiceName() error:&error]; - - if (!appAnonID) { - appAnonID = bit_UUID(); - // store this UUID in the keychain (on this device only) so we can be sure to always have the same ID upon app startups - if (appAnonID) { - // add to keychain in a background thread, since we got reports that storing to the keychain may take several seconds sometimes and cause the app to be killed - // and we don't care about the result anyway - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ - [BITKeychainUtils storeUsername:appAnonIDKey - andPassword:appAnonID - forServiceName:bit_keychainHockeySDKServiceName() - updateExisting:YES - accessibility:kSecAttrAccessibleAlwaysThisDeviceOnly - error:&error]; - }); - } - } - }); - } - - return appAnonID; -} - -#pragma mark Environment detection - -BOOL bit_isPreiOS10Environment(void) { - static BOOL isPreOS10Environment = YES; - static dispatch_once_t checkOS10; - - dispatch_once(&checkOS10, ^{ - // NSFoundationVersionNumber_iOS_9_MAX = 1299 - // We hardcode this, so compiling with iOS 7 is possible while still being able to detect the correct environment - - // runtime check according to - // https://developer.apple.com/library/prerelease/ios/documentation/UserExperience/Conceptual/TransitionGuide/SupportingEarlieriOS.html - if (floor(NSFoundationVersionNumber) <= 1299.00) { - isPreOS10Environment = YES; - } else { - isPreOS10Environment = NO; - } - }); - - return isPreOS10Environment; -} - - -BOOL bit_isAppStoreReceiptSandbox(void) { -#if TARGET_OS_SIMULATOR - return NO; -#else - if (![NSBundle.mainBundle respondsToSelector:@selector(appStoreReceiptURL)]) { - return NO; - } - NSURL *appStoreReceiptURL = NSBundle.mainBundle.appStoreReceiptURL; - NSString *appStoreReceiptLastComponent = appStoreReceiptURL.lastPathComponent; - - BOOL isSandboxReceipt = [appStoreReceiptLastComponent isEqualToString:@"sandboxReceipt"]; - return isSandboxReceipt; -#endif -} - -BOOL bit_hasEmbeddedMobileProvision(void) { - BOOL hasEmbeddedMobileProvision = !![[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"]; - return hasEmbeddedMobileProvision; -} - -BITEnvironment bit_currentAppEnvironment(void) { -#if TARGET_OS_SIMULATOR - return BITEnvironmentOther; -#else - - // MobilePovision profiles are a clear indicator for Ad-Hoc distribution - if (bit_hasEmbeddedMobileProvision()) { - return BITEnvironmentOther; - } - - if (bit_isAppStoreReceiptSandbox()) { - return BITEnvironmentTestFlight; - } - - return BITEnvironmentAppStore; -#endif -} - -BOOL bit_isRunningInAppExtension(void) { - static BOOL isRunningInAppExtension = NO; - static dispatch_once_t checkAppExtension; - - dispatch_once(&checkAppExtension, ^{ - isRunningInAppExtension = ([[[NSBundle mainBundle] executablePath] rangeOfString:@".appex/"].location != NSNotFound); - }); - - return isRunningInAppExtension; -} - -BOOL bit_isDebuggerAttached(void) { - static BOOL debuggerIsAttached = NO; - - static dispatch_once_t debuggerPredicate; - dispatch_once(&debuggerPredicate, ^{ - struct kinfo_proc info; - size_t info_size = sizeof(info); - int name[4]; - - name[0] = CTL_KERN; - name[1] = KERN_PROC; - name[2] = KERN_PROC_PID; - name[3] = getpid(); - - if (sysctl(name, 4, &info, &info_size, NULL, 0) == -1) { - BITHockeyLogError(@"[HockeySDK] ERROR: Checking for a running debugger via sysctl() failed."); - debuggerIsAttached = false; - } - - if (!debuggerIsAttached && (info.kp_proc.p_flag & P_TRACED) != 0) - debuggerIsAttached = true; - }); - - return debuggerIsAttached; -} - -#pragma mark NSString helpers - -NSString *bit_URLEncodedString(NSString *inputString) { - return [inputString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet characterSetWithCharactersInString:@"!*'();:@&=+$,/?%#[] {}"].invertedSet]; -} - -#pragma mark Context helpers - -// Return ISO 8601 string representation of the date -NSString *bit_utcDateString(NSDate *date){ - static NSDateFormatter *dateFormatter; - - static dispatch_once_t dateFormatterToken; - dispatch_once(&dateFormatterToken, ^{ - NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; - dateFormatter = [NSDateFormatter new]; - dateFormatter.locale = enUSPOSIXLocale; - dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; - dateFormatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; - }); - - NSString *dateString = [dateFormatter stringFromDate:date]; - - return dateString; -} - -NSString *bit_devicePlatform(void) { - - size_t size; - sysctlbyname("hw.machine", NULL, &size, NULL, 0); - char *answer = (char*)malloc(size); - if (answer == NULL) - return @""; - sysctlbyname("hw.machine", answer, &size, NULL, 0); - NSString *platform = [NSString stringWithCString:answer encoding: NSUTF8StringEncoding]; - free(answer); - return platform; -} - -NSString *bit_deviceType(void){ - - UIUserInterfaceIdiom idiom = [UIDevice currentDevice].userInterfaceIdiom; - - switch (idiom) { - case UIUserInterfaceIdiomPad: - return @"Tablet"; - case UIUserInterfaceIdiomPhone: - return @"Phone"; - default: - return @"Unknown"; - } -} - -NSString *bit_osVersionBuild(void) { - void *result = NULL; - size_t result_len = 0; - int ret; - - /* If our buffer is too small after allocation, loop until it succeeds -- the requested destination size - * may change after each iteration. */ - do { - /* Fetch the expected length */ - if ((ret = sysctlbyname("kern.osversion", NULL, &result_len, NULL, 0)) == -1) { - break; - } - - /* Allocate the destination buffer */ - if (result != NULL) { - free(result); - } - result = malloc(result_len); - - /* Fetch the value */ - ret = sysctlbyname("kern.osversion", result, &result_len, NULL, 0); - } while (ret == -1 && errno == ENOMEM); - - /* Handle failure */ - if (ret == -1) { - int saved_errno = errno; - - if (result != NULL) { - free(result); - } - - errno = saved_errno; - return NULL; - } - - NSString *osBuild = [NSString stringWithCString:result encoding:NSUTF8StringEncoding]; - free(result); - - NSString *osVersion = [[UIDevice currentDevice] systemVersion]; - - return [NSString stringWithFormat:@"%@ (%@)", osVersion, osBuild]; -} - -NSString *bit_osName(void){ - return [[UIDevice currentDevice] systemName]; -} - -NSString *bit_deviceLocale(void) { - NSLocale *locale = [NSLocale currentLocale]; - return [locale objectForKey:NSLocaleIdentifier]; -} - -NSString *bit_deviceLanguage(void) { - return [[NSBundle mainBundle] preferredLocalizations][0]; -} - -NSString *bit_screenSize(void){ - CGFloat scale = [UIScreen mainScreen].scale; - CGSize screenSize = [UIScreen mainScreen].bounds.size; - return [NSString stringWithFormat:@"%dx%d",(int)(screenSize.height * scale), (int)(screenSize.width * scale)]; -} - -NSString *bit_sdkVersion(void){ - return [NSString stringWithFormat:@"ios:%@", [NSString stringWithUTF8String:BITHOCKEY_C_VERSION]]; -} - -NSString *bit_appVersion(void){ - NSString *build = [[NSBundle mainBundle] infoDictionary][@"CFBundleVersion"]; - NSString *version = [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"]; - - if(version){ - return [NSString stringWithFormat:@"%@ (%@)", version, build]; - }else{ - return build; - } -} - -#if !defined (HOCKEYSDK_CONFIGURATION_ReleaseCrashOnly) && !defined (HOCKEYSDK_CONFIGURATION_ReleaseCrashOnlyExtensions) - -#pragma mark AppIcon helpers - -/** - Find a valid app icon filename that points to a proper app icon image - - @param icons NSArray with app icon filenames - - @return NSString with the valid app icon or nil if none found - */ -NSString *bit_validAppIconStringFromIcons(NSBundle *resourceBundle, NSArray *icons) { - if (!icons) return nil; - if (![icons isKindOfClass:[NSArray class]]) return nil; - - BOOL useHighResIcon = NO; - BOOL useiPadIcon = NO; - if ([UIScreen mainScreen].scale >= (CGFloat) 2.0) useHighResIcon = YES; - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) useiPadIcon = YES; - - NSString *currentBestMatch = nil; - CGFloat currentBestMatchHeight = 0; - CGFloat bestMatchHeight = 0; - - bestMatchHeight = useiPadIcon ? (useHighResIcon ? 152 : 76) : 120; - - for(NSString *icon in icons) { - // Don't use imageNamed, otherwise unit tests won't find the fixture icon - // and using imageWithContentsOfFile doesn't load @2x files with absolut paths (required in tests) - - - NSMutableArray *iconFilenameVariants = [NSMutableArray new]; - - [iconFilenameVariants addObject:icon]; - [iconFilenameVariants addObject:[NSString stringWithFormat:@"%@@2x", icon]]; - [iconFilenameVariants addObject:[icon stringByDeletingPathExtension]]; - [iconFilenameVariants addObject:[NSString stringWithFormat:@"%@@2x", [icon stringByDeletingPathExtension]]]; - - for (NSString *iconFilename in iconFilenameVariants) { - // this call already covers "~ipad" files - NSString *iconPath = [resourceBundle pathForResource:iconFilename ofType:@"png"]; - - if (!iconPath && (icon.pathExtension.length > 0)) { - iconPath = [resourceBundle pathForResource:iconFilename ofType:icon.pathExtension]; - } - // We still haven't managed to get a path to the app icon, just using a placeholder now. - if(!iconPath) { - iconPath = [resourceBundle pathForResource:@"AppIconPlaceHolder" ofType:@"png"]; - } - - NSData *imgData = [[NSData alloc] initWithContentsOfFile:iconPath]; - - UIImage *iconImage = [[UIImage alloc] initWithData:imgData]; - - if (iconImage) { - if (iconImage.size.height == bestMatchHeight) { - return iconFilename; - } else if (iconImage.size.height < bestMatchHeight && - iconImage.size.height > currentBestMatchHeight) { - currentBestMatchHeight = iconImage.size.height; - currentBestMatch = iconFilename; - } - } - } - } - - return currentBestMatch; -} - -NSString *bit_validAppIconFilename(NSBundle *bundle, NSBundle *resourceBundle) { - NSString *iconFilename = nil; - NSArray *icons = nil; - - icons = [bundle objectForInfoDictionaryKey:@"CFBundleIconFiles"]; - iconFilename = bit_validAppIconStringFromIcons(resourceBundle, icons); - - if (!iconFilename) { - icons = [bundle objectForInfoDictionaryKey:@"CFBundleIcons"]; - if (icons && [icons isKindOfClass:[NSDictionary class]]) { - icons = [icons valueForKeyPath:@"CFBundlePrimaryIcon.CFBundleIconFiles"]; - } - iconFilename = bit_validAppIconStringFromIcons(resourceBundle, icons); - } - - // we test iPad structure anyway and use it if we find a result and don't have another one yet - if (!iconFilename && (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)) { - icons = [bundle objectForInfoDictionaryKey:@"CFBundleIcons~ipad"]; - if (icons && [icons isKindOfClass:[NSDictionary class]]) { - icons = [icons valueForKeyPath:@"CFBundlePrimaryIcon.CFBundleIconFiles"]; - } - NSString *iPadIconFilename = bit_validAppIconStringFromIcons(resourceBundle, icons); - iconFilename = iPadIconFilename; - } - - if (!iconFilename) { - NSString *tempFilename = [bundle objectForInfoDictionaryKey:@"CFBundleIconFile"]; - if (tempFilename) { - iconFilename = bit_validAppIconStringFromIcons(resourceBundle, @[tempFilename]); - } - } - - if (!iconFilename) { - iconFilename = bit_validAppIconStringFromIcons(resourceBundle, @[@"Icon.png"]); - } - - return iconFilename; -} - -#pragma mark UIImage private helpers - -static void bit_addRoundedRectToPath(CGRect rect, CGContextRef context, CGFloat ovalWidth, CGFloat ovalHeight); -static CGContextRef bit_MyOpenBitmapContext(int pixelsWide, int pixelsHigh); -static CGImageRef bit_CreateGradientImage(int pixelsWide, int pixelsHigh, CGFloat fromAlpha, CGFloat toAlpha); -static BOOL bit_hasAlpha(UIImage *inputImage); -UIImage *bit_imageWithAlpha(UIImage *inputImage); -UIImage *bit_addGlossToImage(UIImage *inputImage); - -// Adds a rectangular path to the given context and rounds its corners by the given extents -// Original author: Björn Sållarp. Used with permission. See: http://blog.sallarp.com/iphone-uiimage-round-corners/ -void bit_addRoundedRectToPath(CGRect rect, CGContextRef context, CGFloat ovalWidth, CGFloat ovalHeight) { - if (ovalWidth == 0 || ovalHeight == 0) { - CGContextAddRect(context, rect); - return; - } - CGContextSaveGState(context); - CGContextTranslateCTM(context, CGRectGetMinX(rect), CGRectGetMinY(rect)); - CGContextScaleCTM(context, ovalWidth, ovalHeight); - CGFloat fw = CGRectGetWidth(rect) / ovalWidth; - CGFloat fh = CGRectGetHeight(rect) / ovalHeight; - CGContextMoveToPoint(context, fw, fh/2); - CGContextAddArcToPoint(context, fw, fh, fw/2, fh, 1); - CGContextAddArcToPoint(context, 0, fh, 0, fh/2, 1); - CGContextAddArcToPoint(context, 0, 0, fw/2, 0, 1); - CGContextAddArcToPoint(context, fw, 0, fw, fh/2, 1); - CGContextClosePath(context); - CGContextRestoreGState(context); -} - -CGImageRef bit_CreateGradientImage(int pixelsWide, int pixelsHigh, CGFloat fromAlpha, CGFloat toAlpha) { - CGImageRef theCGImage = NULL; - - // gradient is always black-white and the mask must be in the gray colorspace - CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray(); - - // create the bitmap context - CGContextRef gradientBitmapContext = CGBitmapContextCreate(NULL, pixelsWide, pixelsHigh, - 8, 0, colorSpace, (CGBitmapInfo)kCGImageAlphaNone); - - // define the start and end grayscale values (with the alpha, even though - // our bitmap context doesn't support alpha the gradient requires it) - CGFloat colors[] = {toAlpha, 1.0, fromAlpha, 1.0}; - - // create the CGGradient and then release the gray color space - CGGradientRef grayScaleGradient = CGGradientCreateWithColorComponents(colorSpace, colors, NULL, 2); - CGColorSpaceRelease(colorSpace); - - // create the start and end points for the gradient vector (straight down) - CGPoint gradientEndPoint = CGPointZero; - CGPoint gradientStartPoint = CGPointMake(0, pixelsHigh); - - // draw the gradient into the gray bitmap context - CGContextDrawLinearGradient(gradientBitmapContext, grayScaleGradient, gradientStartPoint, - gradientEndPoint, kCGGradientDrawsAfterEndLocation); - CGGradientRelease(grayScaleGradient); - - // convert the context into a CGImageRef and release the context - theCGImage = CGBitmapContextCreateImage(gradientBitmapContext); - CGContextRelease(gradientBitmapContext); - - // return the imageref containing the gradient - return theCGImage; -} - -CGContextRef bit_MyOpenBitmapContext(int pixelsWide, int pixelsHigh) { - CGSize size = CGSizeMake(pixelsWide, pixelsHigh); - UIGraphicsBeginImageContextWithOptions(size, NO, 0.0); - - return UIGraphicsGetCurrentContext(); -} - - -// Returns true if the image has an alpha layer -BOOL bit_hasAlpha(UIImage *inputImage) { - CGImageAlphaInfo alpha = CGImageGetAlphaInfo(inputImage.CGImage); - return (alpha == kCGImageAlphaFirst || - alpha == kCGImageAlphaLast || - alpha == kCGImageAlphaPremultipliedFirst || - alpha == kCGImageAlphaPremultipliedLast); -} - -// Returns a copy of the given image, adding an alpha channel if it doesn't already have one -UIImage *bit_imageWithAlpha(UIImage *inputImage) { - if (bit_hasAlpha(inputImage)) { - return inputImage; - } - - CGImageRef imageRef = inputImage.CGImage; - size_t width = (size_t)(CGImageGetWidth(imageRef) * inputImage.scale); - size_t height = (size_t)(CGImageGetHeight(imageRef) * inputImage.scale); - - // The bitsPerComponent and bitmapInfo values are hard-coded to prevent an "unsupported parameter combination" error - CGContextRef offscreenContext = CGBitmapContextCreate(NULL, - width, - height, - 8, - 0, - CGImageGetColorSpace(imageRef), - kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst); - - // Draw the image into the context and retrieve the new image, which will now have an alpha layer - CGContextDrawImage(offscreenContext, CGRectMake(0, 0, width, height), imageRef); - CGImageRef imageRefWithAlpha = CGBitmapContextCreateImage(offscreenContext); - UIImage *imageWithAlpha = [UIImage imageWithCGImage:imageRefWithAlpha]; - - // Clean up - CGContextRelease(offscreenContext); - CGImageRelease(imageRefWithAlpha); - - return imageWithAlpha; -} - -UIImage *bit_addGlossToImage(UIImage *inputImage) { - UIGraphicsBeginImageContextWithOptions(inputImage.size, NO, 0.0); - - [inputImage drawAtPoint:CGPointZero]; - UIImage *iconGradient = bit_imageNamed(@"IconGradient.png", BITHOCKEYSDK_BUNDLE); - [iconGradient drawInRect:CGRectMake(0, 0, inputImage.size.width, inputImage.size.height) blendMode:kCGBlendModeNormal alpha:0.5]; - - UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - - return result; -} - -#pragma mark UIImage helpers - -UIImage *bit_imageToFitSize(UIImage *inputImage, CGSize fitSize, BOOL honorScaleFactor) { - - if (!inputImage){ - return nil; - } - - CGFloat imageScaleFactor = 1.0; - if (honorScaleFactor) { - if ([inputImage respondsToSelector:@selector(scale)]) { - imageScaleFactor = [inputImage scale]; - } - } - - CGFloat sourceWidth = [inputImage size].width * imageScaleFactor; - CGFloat sourceHeight = [inputImage size].height * imageScaleFactor; - CGFloat targetWidth = fitSize.width; - CGFloat targetHeight = fitSize.height; - - // Calculate aspect ratios - CGFloat sourceRatio = sourceWidth / sourceHeight; - CGFloat targetRatio = targetWidth / targetHeight; - - // Determine what side of the source image to use for proportional scaling - BOOL scaleWidth = (sourceRatio <= targetRatio); - // Deal with the case of just scaling proportionally to fit, without cropping - scaleWidth = !scaleWidth; - - // Proportionally scale source image - CGFloat scalingFactor, scaledWidth, scaledHeight; - if (scaleWidth) { - scalingFactor = ((CGFloat)1.0) / sourceRatio; - scaledWidth = targetWidth; - scaledHeight = round(targetWidth * scalingFactor); - } else { - scalingFactor = sourceRatio; - scaledWidth = round(targetHeight * scalingFactor); - scaledHeight = targetHeight; - } - - // Calculate compositing rectangles - CGRect sourceRect, destRect; - sourceRect = CGRectMake(0, 0, sourceWidth, sourceHeight); - destRect = CGRectMake(0, 0, scaledWidth, scaledHeight); - - // Create appropriately modified image. - UIImage *image = nil; - UIGraphicsBeginImageContextWithOptions(destRect.size, NO, honorScaleFactor ? 0.0 : 1.0); // 0.0 for scale means "correct scale for device's main screen". - CGImageRef sourceImg = CGImageCreateWithImageInRect([inputImage CGImage], sourceRect); // cropping happens here. - image = [UIImage imageWithCGImage:sourceImg scale:0.0 orientation:inputImage.imageOrientation]; // create cropped UIImage. - [image drawInRect:destRect]; // the actual scaling happens here, and orientation is taken care of automatically. - CGImageRelease(sourceImg); - image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - - if (!image) { - // Try older method. - CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); - CGContextRef context = CGBitmapContextCreate(NULL, (size_t)scaledWidth, (size_t)scaledHeight, 8, (size_t)(fitSize.width * 4), - colorSpace, (CGBitmapInfo)kCGImageAlphaPremultipliedLast); - sourceImg = CGImageCreateWithImageInRect([inputImage CGImage], sourceRect); - CGContextDrawImage(context, destRect, sourceImg); - CGImageRelease(sourceImg); - CGImageRef finalImage = CGBitmapContextCreateImage(context); - CGContextRelease(context); - CGColorSpaceRelease(colorSpace); - image = [UIImage imageWithCGImage:finalImage]; - CGImageRelease(finalImage); - } - - return image; -} - - -UIImage *bit_reflectedImageWithHeight(UIImage *inputImage, NSUInteger height, CGFloat fromAlpha, CGFloat toAlpha) { - if(height == 0) - return nil; - - // create a bitmap graphics context the size of the image - CGContextRef mainViewContentContext = bit_MyOpenBitmapContext((int)inputImage.size.width, (int)height); - - // create a 2 bit CGImage containing a gradient that will be used for masking the - // main view content to create the 'fade' of the reflection. The CGImageCreateWithMask - // function will stretch the bitmap image as required, so we can create a 1 pixel wide gradient - CGImageRef gradientMaskImage = bit_CreateGradientImage(1, (int)height, fromAlpha, toAlpha); - - // create an image by masking the bitmap of the mainView content with the gradient view - // then release the pre-masked content bitmap and the gradient bitmap - CGContextClipToMask(mainViewContentContext, CGRectMake(0.0, 0.0, inputImage.size.width, height), gradientMaskImage); - CGImageRelease(gradientMaskImage); - - // draw the image into the bitmap context - CGContextDrawImage(mainViewContentContext, CGRectMake(0, 0, inputImage.size.width, inputImage.size.height), inputImage.CGImage); - - // convert the finished reflection image to a UIImage - UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext(); // returns autoreleased - UIGraphicsEndImageContext(); - - return theImage; -} - - -UIImage *bit_newWithContentsOfResolutionIndependentFile(NSString * path) { - if ([UIScreen instancesRespondToSelector:@selector(scale)] && (int)[[UIScreen mainScreen] scale] == 2.0) { - NSString *path2x = [[path stringByDeletingLastPathComponent] - stringByAppendingPathComponent:[NSString stringWithFormat:@"%@@2x.%@", - [[path lastPathComponent] stringByDeletingPathExtension], - [path pathExtension]]]; - - if ([[NSFileManager defaultManager] fileExistsAtPath:path2x]) { - return [[UIImage alloc] initWithContentsOfFile:path2x]; - } - } - - return [[UIImage alloc] initWithContentsOfFile:path]; -} - - -UIImage *bit_imageWithContentsOfResolutionIndependentFile(NSString *path) { - return bit_newWithContentsOfResolutionIndependentFile(path); -} - - -UIImage *bit_imageNamed(NSString *imageName, NSString *bundleName) { - NSString *resourcePath = [[NSBundle bundleForClass:[BITHockeyManager class]] resourcePath]; - NSString *bundlePath = [resourcePath stringByAppendingPathComponent:bundleName]; - NSString *imagePath = [bundlePath stringByAppendingPathComponent:imageName]; - return bit_imageWithContentsOfResolutionIndependentFile(imagePath); -} - - - -// Creates a copy of this image with rounded corners -// If borderSize is non-zero, a transparent border of the given size will also be added -// Original author: Björn Sållarp. Used with permission. See: http://blog.sallarp.com/iphone-uiimage-round-corners/ -UIImage *bit_roundedCornerImage(UIImage *inputImage, CGFloat cornerSize, NSInteger borderSize) { - // If the image does not have an alpha layer, add one - - UIImage *roundedImage = nil; - UIGraphicsBeginImageContextWithOptions(inputImage.size, NO, 0.0); // 0.0 for scale means "correct scale for device's main screen". - CGImageRef sourceImg = CGImageCreateWithImageInRect([inputImage CGImage], CGRectMake(0, 0, inputImage.size.width * inputImage.scale, inputImage.size.height * inputImage.scale)); // cropping happens here. - - // Create a clipping path with rounded corners - CGContextRef context = UIGraphicsGetCurrentContext(); - CGContextBeginPath(context); - bit_addRoundedRectToPath(CGRectMake(borderSize, borderSize, inputImage.size.width - borderSize * 2, inputImage.size.height - borderSize * 2), context, cornerSize, cornerSize); - CGContextClosePath(context); - CGContextClip(context); - - roundedImage = [UIImage imageWithCGImage:sourceImg scale:0.0 orientation:inputImage.imageOrientation]; // create cropped UIImage. - [roundedImage drawInRect:CGRectMake(0, 0, inputImage.size.width, inputImage.size.height)]; // the actual scaling happens here, and orientation is taken care of automatically. - CGImageRelease(sourceImg); - roundedImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - - if (!roundedImage) { - // Try older method. - UIImage *image = bit_imageWithAlpha(inputImage); - - // Build a context that's the same dimensions as the new size - context = CGBitmapContextCreate(NULL, - (size_t)image.size.width, - (size_t)image.size.height, - CGImageGetBitsPerComponent(image.CGImage), - 0, - CGImageGetColorSpace(image.CGImage), - CGImageGetBitmapInfo(image.CGImage)); - - // Create a clipping path with rounded corners - CGContextBeginPath(context); - bit_addRoundedRectToPath(CGRectMake(borderSize, borderSize, image.size.width - borderSize * 2, image.size.height - borderSize * 2), context, cornerSize, cornerSize); - CGContextClosePath(context); - CGContextClip(context); - - // Draw the image to the context; the clipping path will make anything outside the rounded rect transparent - CGContextDrawImage(context, CGRectMake(0, 0, image.size.width, image.size.height), image.CGImage); - - // Create a CGImage from the context - CGImageRef clippedImage = CGBitmapContextCreateImage(context); - CGContextRelease(context); - - // Create a UIImage from the CGImage - roundedImage = [UIImage imageWithCGImage:clippedImage]; - CGImageRelease(clippedImage); - } - - return roundedImage; -} - -UIImage *bit_appIcon() { - NSString *iconString = [NSString string]; - NSArray *icons = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIconFiles"]; - if (!icons) { - icons = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIcons"]; - if ((icons) && ([icons isKindOfClass:[NSDictionary class]])) { - icons = [icons valueForKeyPath:@"CFBundlePrimaryIcon.CFBundleIconFiles"]; - } - - if (!icons) { - iconString = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIconFile"]; - if (!iconString) { - iconString = @"Icon.png"; - } - } - } - - if (icons) { - BOOL useHighResIcon = NO; - if ([UIScreen mainScreen].scale >= 2) useHighResIcon = YES; - - for(NSString *icon in icons) { - iconString = icon; - UIImage *iconImage = [UIImage imageNamed:icon]; - - if (iconImage.size.height == 57 && !useHighResIcon) { - // found! - break; - } - if (iconImage.size.height == 114 && useHighResIcon) { - // found! - break; - } - } - } - - BOOL addGloss = YES; - NSNumber *prerendered = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIPrerenderedIcon"]; - if (prerendered) { - addGloss = ![prerendered boolValue]; - } - - if (addGloss) { - return bit_addGlossToImage([UIImage imageNamed:iconString]); - } else { - return [UIImage imageNamed:iconString]; - } -} - -UIImage *bit_screenshot(void) { - // Create a graphics context with the target size - CGSize imageSize = [[UIScreen mainScreen] bounds].size; - BOOL isLandscapeLeft = [UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeLeft; - BOOL isLandscapeRight = [UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeRight; - BOOL isUpsideDown = [UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationPortraitUpsideDown; - - BOOL needsRotation = NO; - - if ((isLandscapeLeft ||isLandscapeRight) && imageSize.height > imageSize.width) { - needsRotation = YES; - CGFloat temp = imageSize.width; - imageSize.width = imageSize.height; - imageSize.height = temp; - } - - UIGraphicsBeginImageContextWithOptions(imageSize, YES, 0); - - CGContextRef context = UIGraphicsGetCurrentContext(); - - // Iterate over every window from back to front - //NSInteger count = 0; - for (UIWindow *window in [[UIApplication sharedApplication] windows]) { - if (![window respondsToSelector:@selector(screen)] || [window screen] == [UIScreen mainScreen]) { - // -renderInContext: renders in the coordinate space of the layer, - // so we must first apply the layer's geometry to the graphics context - CGContextSaveGState(context); - - // Center the context around the window's anchor point - CGContextTranslateCTM(context, [window center].x, [window center].y); - - // Apply the window's transform about the anchor point - CGContextConcatCTM(context, [window transform]); - - // Offset by the portion of the bounds left of and above the anchor point - CGContextTranslateCTM(context, - -[window bounds].size.width * [[window layer] anchorPoint].x, - -[window bounds].size.height * [[window layer] anchorPoint].y); - - if (needsRotation) { - if (isLandscapeLeft) { - CGContextConcatCTM(context, CGAffineTransformRotate(CGAffineTransformMakeTranslation( imageSize.width, 0), (CGFloat)M_PI_2)); - } else if (isLandscapeRight) { - CGContextConcatCTM(context, CGAffineTransformRotate(CGAffineTransformMakeTranslation( 0, imageSize.height), 3 * (CGFloat)M_PI_2)); - } - } else if (isUpsideDown) { - CGContextConcatCTM(context, CGAffineTransformRotate(CGAffineTransformMakeTranslation( imageSize.width, imageSize.height), (CGFloat)M_PI)); - } - - if ([window respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) { - [window drawViewHierarchyInRect:window.bounds afterScreenUpdates:NO]; - } else { - // Render the layer hierarchy to the current context - [[window layer] renderInContext:context]; - } - - // Restore the context - CGContextRestoreGState(context); - } - } - - // Retrieve the screenshot image - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); - - UIGraphicsEndImageContext(); - - return image; -} - -#endif /* HOCKEYSDK_CONFIGURATION_ReleaseCrashOnly && HOCKEYSDK_CONFIGURATION_ReleaseCrashOnlyExtensions */ diff --git a/submodules/HockeySDK-iOS/Classes/BITHockeyLogger.h b/submodules/HockeySDK-iOS/Classes/BITHockeyLogger.h deleted file mode 100644 index 83a7169673..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITHockeyLogger.h +++ /dev/null @@ -1,21 +0,0 @@ -// Adapted from 0xced’s post at http://stackoverflow.com/questions/34732814/how-should-i-handle-logs-in-an-objective-c-library/34732815#34732815 - -#import -#import "HockeySDKEnums.h" - -#define BITHockeyLog(_level, _message) [BITHockeyLogger logMessage:_message level:_level file:__FILE__ function:__PRETTY_FUNCTION__ line:__LINE__] - -#define BITHockeyLogError(format, ...) BITHockeyLog(BITLogLevelError, (^{ return [NSString stringWithFormat:(format), ##__VA_ARGS__]; })) -#define BITHockeyLogWarning(format, ...) BITHockeyLog(BITLogLevelWarning, (^{ return [NSString stringWithFormat:(format), ##__VA_ARGS__]; })) -#define BITHockeyLogDebug(format, ...) BITHockeyLog(BITLogLevelDebug, (^{ return [NSString stringWithFormat:(format), ##__VA_ARGS__]; })) -#define BITHockeyLogVerbose(format, ...) BITHockeyLog(BITLogLevelVerbose, (^{ return [NSString stringWithFormat:(format), ##__VA_ARGS__]; })) - -@interface BITHockeyLogger : NSObject - -+ (BITLogLevel)currentLogLevel; -+ (void)setCurrentLogLevel:(BITLogLevel)currentLogLevel; -+ (void)setLogHandler:(BITLogHandler)logHandler; - -+ (void)logMessage:(BITLogMessageProvider)messageProvider level:(BITLogLevel)loglevel file:(const char *)file function:(const char *)function line:(uint)line; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITHockeyLogger.m b/submodules/HockeySDK-iOS/Classes/BITHockeyLogger.m deleted file mode 100644 index 1a39c23dd3..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITHockeyLogger.m +++ /dev/null @@ -1,42 +0,0 @@ -#import "BITHockeyLoggerPrivate.h" -#import "HockeySDK.h" - -@implementation BITHockeyLogger - -static BITLogLevel _currentLogLevel = BITLogLevelWarning; -static BITLogHandler currentLogHandler; - -BITLogHandler const defaultLogHandler = ^(BITLogMessageProvider messageProvider, BITLogLevel logLevel, const char __unused *file, const char *function, uint line) { - if (messageProvider) { - if (_currentLogLevel < logLevel) { - return; - } - NSString *functionString = [NSString stringWithUTF8String:function]; - NSLog((@"[HockeySDK] %@/%d %@"), functionString, line, messageProvider()); - } -}; - - -+ (void)initialize { - currentLogHandler = defaultLogHandler; -} - -+ (BITLogLevel)currentLogLevel { - return _currentLogLevel; -} - -+ (void)setCurrentLogLevel:(BITLogLevel)currentLogLevel { - _currentLogLevel = currentLogLevel; -} - -+ (void)setLogHandler:(BITLogHandler)logHandler { - currentLogHandler = logHandler; -} - -+ (void)logMessage:(BITLogMessageProvider)messageProvider level:(BITLogLevel)loglevel file:(const char *)file function:(const char *)function line:(uint)line { - if (currentLogHandler) { - currentLogHandler(messageProvider, loglevel, file, function, line); - } -} - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITHockeyLoggerPrivate.h b/submodules/HockeySDK-iOS/Classes/BITHockeyLoggerPrivate.h deleted file mode 100644 index 279cd11384..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITHockeyLoggerPrivate.h +++ /dev/null @@ -1,3 +0,0 @@ -#import "BITHockeyLogger.h" - -FOUNDATION_EXPORT BITLogHandler const defaultLogHandler; diff --git a/submodules/HockeySDK-iOS/Classes/BITHockeyManager.h b/submodules/HockeySDK-iOS/Classes/BITHockeyManager.h deleted file mode 100644 index 1060a1f298..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITHockeyManager.h +++ /dev/null @@ -1,618 +0,0 @@ -/* - * Author: Andreas Linde - * Kent Sutherland - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import -#import - -#import "HockeySDKFeatureConfig.h" -#import "HockeySDKEnums.h" - -@protocol BITHockeyManagerDelegate; - -@class BITHockeyBaseManager; -#if HOCKEYSDK_FEATURE_CRASH_REPORTER -@class BITCrashManager; -#endif -#if HOCKEYSDK_FEATURE_UPDATES -@class BITUpdateManager; -#endif -#if HOCKEYSDK_FEATURE_STORE_UPDATES -@class BITStoreUpdateManager; -#endif -#if HOCKEYSDK_FEATURE_FEEDBACK -@class BITFeedbackManager; -#endif -#if HOCKEYSDK_FEATURE_AUTHENTICATOR -@class BITAuthenticator; -#endif -#if HOCKEYSDK_FEATURE_METRICS -@class BITMetricsManager; -#endif - -/** - The HockeySDK manager. Responsible for setup and management of all components - - This is the principal SDK class. It represents the entry point for the HockeySDK. The main promises of the class are initializing the SDK modules, providing access to global properties and to all modules. Initialization is divided into several distinct phases: - - 1. Setup the [HockeyApp](http://hockeyapp.net/) app identifier and the optional delegate: This is the least required information on setting up the SDK and using it. It does some simple validation of the app identifier and checks if the app is running from the App Store or not. - 2. Provides access to the SDK modules `BITCrashManager`, `BITUpdateManager`, and `BITFeedbackManager`. This way all modules can be further configured to personal needs, if the defaults don't fit the requirements. - 3. Configure each module. - 4. Start up all modules. - - The SDK is optimized to defer everything possible to a later time while making sure e.g. crashes on startup can also be caught and each module executes other code with a delay some seconds. This ensures that applicationDidFinishLaunching will process as fast as possible and the SDK will not block the startup sequence resulting in a possible kill by the watchdog process. - - All modules do **NOT** show any user interface if the module is not activated or not integrated. - `BITCrashManager`: Shows an alert on startup asking the user if he/she agrees on sending the crash report, if `[BITCrashManager crashManagerStatus]` is set to `BITCrashManagerStatusAlwaysAsk` (default) - `BITUpdateManager`: Is automatically deactivated when the SDK detects it is running from a build distributed via the App Store. Otherwise if it is not deactivated manually, it will show an alert after startup informing the user about a pending update, if one is available. If the user then decides to view the update another screen is presented with further details and an option to install the update. - `BITFeedbackManager`: If this module is deactivated or the user interface is nowhere added into the app, this module will not do anything. It will not fetch the server for data or show any user interface. If it is integrated, activated, and the user already used it to provide feedback, it will show an alert after startup if a new answer has been received from the server with the option to view it. - - Example: - - [[BITHockeyManager sharedHockeyManager] - configureWithIdentifier:@"" - delegate:nil]; - [[BITHockeyManager sharedHockeyManager] startManager]; - - @warning The SDK is **NOT** thread safe and has to be set up on the main thread! - - @warning Most properties of all components require to be set **BEFORE** calling`startManager`! - - */ - -#import "HockeySDKNullability.h" -NS_ASSUME_NONNULL_BEGIN - -@interface BITHockeyManager: NSObject - -#pragma mark - Public Methods - -///----------------------------------------------------------------------------- -/// @name Initialization -///----------------------------------------------------------------------------- - -/** - Returns a shared BITHockeyManager object - - @return A singleton BITHockeyManager instance ready use - */ -+ (BITHockeyManager *)sharedHockeyManager; - - -/** - Initializes the manager with a particular app identifier - - Initialize the manager with a HockeyApp app identifier. - - [[BITHockeyManager sharedHockeyManager] - configureWithIdentifier:@""]; - - @see configureWithIdentifier:delegate: - @see configureWithBetaIdentifier:liveIdentifier:delegate: - @see startManager - @param appIdentifier The app identifier that should be used. - */ -- (void)configureWithIdentifier:(NSString *)appIdentifier; - - -/** - Initializes the manager with a particular app identifier and delegate - - Initialize the manager with a HockeyApp app identifier and assign the class that - implements the optional protocols `BITHockeyManagerDelegate`, `BITCrashManagerDelegate` or - `BITUpdateManagerDelegate`. - - [[BITHockeyManager sharedHockeyManager] - configureWithIdentifier:@"" - delegate:nil]; - - @see configureWithIdentifier: - @see configureWithBetaIdentifier:liveIdentifier:delegate: - @see startManager - @see BITHockeyManagerDelegate - @see BITCrashManagerDelegate - @see BITUpdateManagerDelegate - @see BITFeedbackManagerDelegate - @param appIdentifier The app identifier that should be used. - @param delegate `nil` or the class implementing the option protocols - */ -- (void)configureWithIdentifier:(NSString *)appIdentifier delegate:(nullable id)delegate; - - -/** - Initializes the manager with an app identifier for beta, one for live usage and delegate - - Initialize the manager with different HockeyApp app identifiers for beta and live usage. - All modules will automatically detect if the app is running in the App Store and use - the live app identifier for that. In all other cases it will use the beta app identifier. - And also assign the class that implements the optional protocols `BITHockeyManagerDelegate`, - `BITCrashManagerDelegate` or `BITUpdateManagerDelegate` - - [[BITHockeyManager sharedHockeyManager] - configureWithBetaIdentifier:@"" - liveIdentifier:@"" - delegate:nil]; - - We recommend using one app entry on HockeyApp for your beta versions and another one for - your live versions. The reason is that you will have way more beta versions than live - versions, but on the other side get way more crash reports on the live version. Separating - them into two different app entries makes it easier to work with the data. In addition - you will likely end up having the same version number for a beta and live version which - would mix different data into the same version. Also the live version does not require - you to upload any IPA files, uploading only the dSYM package for crash reporting is - just fine. - - @see configureWithIdentifier: - @see configureWithIdentifier:delegate: - @see startManager - @see BITHockeyManagerDelegate - @see BITCrashManagerDelegate - @see BITUpdateManagerDelegate - @see BITFeedbackManagerDelegate - @param betaIdentifier The app identifier for the _non_ app store (beta) configurations - @param liveIdentifier The app identifier for the app store configurations. - @param delegate `nil` or the class implementing the optional protocols - */ -- (void)configureWithBetaIdentifier:(NSString *)betaIdentifier liveIdentifier:(NSString *)liveIdentifier delegate:(nullable id)delegate; - - -/** - Starts the manager and runs all modules - - Call this after configuring the manager and setting up all modules. - - @see configureWithIdentifier:delegate: - @see configureWithBetaIdentifier:liveIdentifier:delegate: - */ -- (void)startManager; - -#pragma mark - Public Properties - -///----------------------------------------------------------------------------- -/// @name Modules -///----------------------------------------------------------------------------- - - -/** - Set the delegate - - Defines the class that implements the optional protocol `BITHockeyManagerDelegate`. - - The delegate will automatically be propagated to all components. There is no need to set the delegate - for each component individually. - - @warning This property needs to be set before calling `startManager` - - @see BITHockeyManagerDelegate - @see BITCrashManagerDelegate - @see BITUpdateManagerDelegate - @see BITFeedbackManagerDelegate - */ -@property (nonatomic, weak, nullable) id delegate; - - -/** - Defines the server URL to send data to or request data from - - By default this is set to the HockeyApp servers and there rarely should be a - need to modify that. - Please be aware that the URL for `BITMetricsManager` needs to be set separately - as this class uses a different endpoint! - - @warning This property needs to be set before calling `startManager` - */ -@property (nonatomic, copy) NSString *serverURL; - - -#if HOCKEYSDK_FEATURE_CRASH_REPORTER - -/** - Reference to the initialized BITCrashManager module - - Returns the BITCrashManager instance initialized by BITHockeyManager - - @see configureWithIdentifier:delegate: - @see configureWithBetaIdentifier:liveIdentifier:delegate: - @see startManager - @see disableCrashManager - */ -@property (nonatomic, strong, readonly) BITCrashManager *crashManager; - - -/** - Flag the determines whether the Crash Manager should be disabled - - If this flag is enabled, then crash reporting is disabled and no crashes will - be send. - - Please note that the Crash Manager instance will be initialized anyway, but crash report - handling (signal and uncaught exception handlers) will **not** be registered. - - @warning This property needs to be set before calling `startManager` - - *Default*: _NO_ - @see crashManager - */ -@property (nonatomic, getter = isCrashManagerDisabled) BOOL disableCrashManager; - -#endif - - -#if HOCKEYSDK_FEATURE_UPDATES - -/** - Reference to the initialized BITUpdateManager module - - Returns the BITUpdateManager instance initialized by BITHockeyManager - - @see configureWithIdentifier:delegate: - @see configureWithBetaIdentifier:liveIdentifier:delegate: - @see startManager - @see disableUpdateManager - */ -@property (nonatomic, strong, readonly) BITUpdateManager *updateManager; - - -/** - Flag the determines whether the Update Manager should be disabled - - If this flag is enabled, then checking for updates and submitting beta usage - analytics will be turned off! - - Please note that the Update Manager instance will be initialized anyway! - - @warning This property needs to be set before calling `startManager` - - *Default*: _NO_ - @see updateManager - */ -@property (nonatomic, getter = isUpdateManagerDisabled) BOOL disableUpdateManager; - -#endif - - -#if HOCKEYSDK_FEATURE_STORE_UPDATES - -/** - Reference to the initialized BITStoreUpdateManager module - - Returns the BITStoreUpdateManager instance initialized by BITHockeyManager - - @see configureWithIdentifier:delegate: - @see configureWithBetaIdentifier:liveIdentifier:delegate: - @see startManager - @see enableStoreUpdateManager - */ -@property (nonatomic, strong, readonly) BITStoreUpdateManager *storeUpdateManager; - - -/** - Flag the determines whether the App Store Update Manager should be enabled - - If this flag is enabled, then checking for updates when the app runs from the - app store will be turned on! - - Please note that the Store Update Manager instance will be initialized anyway! - - @warning This property needs to be set before calling `startManager` - - *Default*: _NO_ - @see storeUpdateManager - */ -@property (nonatomic, getter = isStoreUpdateManagerEnabled) BOOL enableStoreUpdateManager; - -#endif - - -#if HOCKEYSDK_FEATURE_FEEDBACK - -/** - Reference to the initialized BITFeedbackManager module - - Returns the BITFeedbackManager instance initialized by BITHockeyManager - - @see configureWithIdentifier:delegate: - @see configureWithBetaIdentifier:liveIdentifier:delegate: - @see startManager - @see disableFeedbackManager - */ -@property (nonatomic, strong, readonly) BITFeedbackManager *feedbackManager; - - -/** - Flag the determines whether the Feedback Manager should be disabled - - If this flag is enabled, then letting the user give feedback and - get responses will be turned off! - - Please note that the Feedback Manager instance will be initialized anyway! - - @warning This property needs to be set before calling `startManager` - - *Default*: _NO_ - @see feedbackManager - */ -@property (nonatomic, getter = isFeedbackManagerDisabled) BOOL disableFeedbackManager; - -#endif - - -#if HOCKEYSDK_FEATURE_AUTHENTICATOR - -/** - Reference to the initialized BITAuthenticator module - - Returns the BITAuthenticator instance initialized by BITHockeyManager - - @see configureWithIdentifier:delegate: - @see configureWithBetaIdentifier:liveIdentifier:delegate: - @see startManager - */ -@property (nonatomic, strong, readonly) BITAuthenticator *authenticator; - -#endif - -#if HOCKEYSDK_FEATURE_METRICS - -/** - Reference to the initialized BITMetricsManager module - - Returns the BITMetricsManager instance initialized by BITHockeyManager - */ -@property (nonatomic, strong, readonly) BITMetricsManager *metricsManager; - -/** - Flag the determines whether the BITMetricsManager should be disabled - - If this flag is enabled, then sending metrics data such as sessions and users - will be turned off! - - Please note that the BITMetricsManager instance will be initialized anyway! - - *Default*: _NO_ - @see metricsManager - */ -@property (nonatomic, getter = isMetricsManagerDisabled) BOOL disableMetricsManager; - -#endif - -///----------------------------------------------------------------------------- -/// @name Environment -///----------------------------------------------------------------------------- - - -/** - Enum that indicates what kind of environment the application is installed and running in. - - This property can be used to disable or enable specific funtionality - only when specific conditions are met. - That could mean for example, to only enable debug UI elements - when the app has been installed over HockeyApp but not in the AppStore. - - The underlying enum type at the moment only specifies values for the AppStore, - TestFlight and Other. Other summarizes several different distribution methods - and we might define additional specifc values for other environments in the future. - - @see BITEnvironment - */ -@property (nonatomic, readonly) BITEnvironment appEnvironment; - - -/** - Returns the app installation specific anonymous UUID - - The value returned by this method is unique and persisted per app installation - in the keychain. It is also being used in crash reports as `CrashReporter Key` - and internally when sending crash reports and feedback messages. - - This is not identical to the `[ASIdentifierManager advertisingIdentifier]` or - the `[UIDevice identifierForVendor]`! - */ -@property (nonatomic, readonly, copy) NSString *installString; - - -/** - Disable tracking the installation of an app on a device - - This will cause the app to generate a new `installString` value every time the - app is cold started. - - This property is only considered in App Store Environment, since it would otherwise - affect the `BITUpdateManager` and `BITAuthenticator` functionalities! - - @warning This property needs to be set before calling `startManager` - - *Default*: _NO_ - */ -@property (nonatomic, getter=isInstallTrackingDisabled) BOOL disableInstallTracking; - -///----------------------------------------------------------------------------- -/// @name Debug Logging -///----------------------------------------------------------------------------- - -/** - This property is used indicate the amount of verboseness and severity for which - you want to see log messages in the console. - */ -@property (nonatomic, assign) BITLogLevel logLevel; - -/** - Flag that determines whether additional logging output should be generated - by the manager and all modules. - - This is ignored if the app is running in the App Store and reverts to the - default value in that case. - - @warning This property needs to be set before calling `startManager` - - *Default*: _NO_ - */ -@property (nonatomic, assign, getter=isDebugLogEnabled) BOOL debugLogEnabled DEPRECATED_MSG_ATTRIBUTE("Use logLevel instead!"); - -/** - Set a custom block that handles all the log messages that are emitted from the SDK. - - You can use this to reroute the messages that would normally be logged by `NSLog();` - to your own custom logging framework. - - An example of how to do this with NSLogger: - - ``` - [[BITHockeyManager sharedHockeyManager] setLogHandler:^(BITLogMessageProvider messageProvider, BITLogLevel logLevel, const char *file, const char *function, uint line) { - LogMessageRawF(file, (int)line, function, @"HockeySDK", (int)logLevel-1, messageProvider()); - }]; - ``` - - or with CocoaLumberjack: - - ``` - [[BITHockeyManager sharedHockeyManager] setLogHandler:^(BITLogMessageProvider messageProvider, BITLogLevel logLevel, const char *file, const char *function, uint line) { - [DDLog log:YES message:messageProvider() level:ddLogLevel flag:(DDLogFlag)(1 << (logLevel-1)) context:<#CocoaLumberjackContext#> file:file function:function line:line tag:nil]; - }]; - ``` - - @param logHandler The block of type BITLogHandler that will process all logged messages. - */ -- (void)setLogHandler:(BITLogHandler)logHandler; - - -///----------------------------------------------------------------------------- -/// @name Integration test -///----------------------------------------------------------------------------- - -/** - Pings the server with the HockeyApp app identifiers used for initialization - - Call this method once for debugging purposes to test if your SDK setup code - reaches the server successfully. - - Once invoked, check the apps page on HockeyApp for a verification. - - If you setup the SDK with a beta and live identifier, a call to both app IDs will be done. - - This call is ignored if the app is running in the App Store!. - */ -- (void)testIdentifier; - - -///----------------------------------------------------------------------------- -/// @name Additional meta data -///----------------------------------------------------------------------------- - -/** Set the userid that should used in the SDK components - - Right now this is used by the `BITCrashManager` to attach to a crash report. - `BITFeedbackManager` uses it too for assigning the user to a discussion thread. - - The value can be set at any time and will be stored in the keychain on the current - device only! To delete the value from the keychain set the value to `nil`. - - This property is optional and can be used as an alternative to the delegate. If you - want to define specific data for each component, use the delegate instead which does - overwrite the values set by this property. - - @warning When returning a non nil value, crash reports are not anonymous any more - and the crash alerts will not show the word "anonymous"! - - @warning This property needs to be set before calling `startManager` to be considered - for being added to crash reports as meta data. - - @see userName - @see userEmail - @see `[BITHockeyManagerDelegate userIDForHockeyManager:componentManager:]` - */ -@property (nonatomic, copy, nullable) NSString *userID; - - -/** Set the user name that should used in the SDK components - - Right now this is used by the `BITCrashManager` to attach to a crash report. - `BITFeedbackManager` uses it too for assigning the user to a discussion thread. - - The value can be set at any time and will be stored in the keychain on the current - device only! To delete the value from the keychain set the value to `nil`. - - This property is optional and can be used as an alternative to the delegate. If you - want to define specific data for each component, use the delegate instead which does - overwrite the values set by this property. - - @warning When returning a non nil value, crash reports are not anonymous any more - and the crash alerts will not show the word "anonymous"! - - @warning This property needs to be set before calling `startManager` to be considered - for being added to crash reports as meta data. - - @see userID - @see userEmail - @see `[BITHockeyManagerDelegate userNameForHockeyManager:componentManager:]` - */ -@property (nonatomic, copy, nullable) NSString *userName; - - -/** Set the users email address that should used in the SDK components - - Right now this is used by the `BITCrashManager` to attach to a crash report. - `BITFeedbackManager` uses it too for assigning the user to a discussion thread. - - The value can be set at any time and will be stored in the keychain on the current - device only! To delete the value from the keychain set the value to `nil`. - - This property is optional and can be used as an alternative to the delegate. If you - want to define specific data for each component, use the delegate instead which does - overwrite the values set by this property. - - @warning When returning a non nil value, crash reports are not anonymous any more - and the crash alerts will not show the word "anonymous"! - - @warning This property needs to be set before calling `startManager` to be considered - for being added to crash reports as meta data. - - @see userID - @see userName - @see [BITHockeyManagerDelegate userEmailForHockeyManager:componentManager:] - */ -@property (nonatomic, copy, nullable) NSString *userEmail; - - -///----------------------------------------------------------------------------- -/// @name SDK meta data -///----------------------------------------------------------------------------- - -/** - Returns the SDK Version (CFBundleShortVersionString). - */ -- (NSString *)version; - -/** - Returns the SDK Build (CFBundleVersion) as a string. - */ -- (NSString *)build; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/HockeySDK-iOS/Classes/BITHockeyManager.m b/submodules/HockeySDK-iOS/Classes/BITHockeyManager.m deleted file mode 100644 index 2a81056fad..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITHockeyManager.m +++ /dev/null @@ -1,750 +0,0 @@ -/* - * Author: Andreas Linde - * Kent Sutherland - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "HockeySDK.h" -#import "HockeySDKPrivate.h" - -#if HOCKEYSDK_FEATURE_CRASH_REPORTER || HOCKEYSDK_FEATURE_FEEDBACK || HOCKEYSDK_FEATURE_UPDATES || HOCKEYSDK_FEATURE_AUTHENTICATOR || HOCKEYSDK_FEATURE_STORE_UPDATES || HOCKEYSDK_FEATURE_METRICS -#import "BITHockeyBaseManagerPrivate.h" -#endif - -#import "BITHockeyHelper.h" -#import "BITHockeyAppClient.h" -#import "BITKeychainUtils.h" - -#include - -typedef struct { - uint8_t info_version; - const char hockey_version[16]; - const char hockey_build[16]; -} bitstadium_info_t; - -#if HOCKEYSDK_FEATURE_CRASH_REPORTER -#import "BITCrashManagerPrivate.h" -#endif /* HOCKEYSDK_FEATURE_CRASH_REPORTER */ - -#if HOCKEYSDK_FEATURE_UPDATES -#import "BITUpdateManagerPrivate.h" -#endif /* HOCKEYSDK_FEATURE_UPDATES */ - -#if HOCKEYSDK_FEATURE_STORE_UPDATES -#import "BITStoreUpdateManagerPrivate.h" -#endif /* HOCKEYSDK_FEATURE_STORE_UPDATES */ - -#if HOCKEYSDK_FEATURE_FEEDBACK -#import "BITFeedbackManagerPrivate.h" -#endif /* HOCKEYSDK_FEATURE_FEEDBACK */ - -#if HOCKEYSDK_FEATURE_AUTHENTICATOR -#import "BITAuthenticator_Private.h" -#endif /* HOCKEYSDK_FEATURE_AUTHENTICATOR */ - -#if HOCKEYSDK_FEATURE_METRICS -#import "BITMetricsManagerPrivate.h" -#import "BITCategoryContainer.h" -#endif /* HOCKEYSDK_FEATURE_METRICS */ - -@interface BITHockeyManager () - -- (BOOL)shouldUseLiveIdentifier; - -@property (nonatomic, copy) NSString *appIdentifier; -@property (nonatomic, copy) NSString *liveIdentifier; -@property (nonatomic) BOOL validAppIdentifier; -@property (nonatomic) BOOL startManagerIsInvoked; -@property (nonatomic) BOOL startUpdateManagerIsInvoked; -@property (nonatomic) BOOL managersInitialized; -@property (nonatomic, strong) BITHockeyAppClient *hockeyAppClient; - -// Redeclare BITHockeyManager properties with readwrite attribute. -@property (nonatomic, readwrite, copy) NSString *installString; - -#if HOCKEYSDK_FEATURE_CRASH_REPORTER -@property (nonatomic, strong, readwrite) BITCrashManager *crashManager; -#endif - -#if HOCKEYSDK_FEATURE_UPDATES -@property (nonatomic, strong, readwrite) BITUpdateManager *updateManager; -#endif - -#if HOCKEYSDK_FEATURE_STORE_UPDATES -@property (nonatomic, strong, readwrite) BITStoreUpdateManager *storeUpdateManager; -#endif - -#if HOCKEYSDK_FEATURE_FEEDBACK -@property (nonatomic, strong, readwrite) BITFeedbackManager *feedbackManager; -#endif - -#if HOCKEYSDK_FEATURE_AUTHENTICATOR -@property (nonatomic, strong, readwrite) BITAuthenticator *authenticator; -#endif - -#if HOCKEYSDK_FEATURE_METRICS -@property (nonatomic, strong, readwrite) BITMetricsManager *metricsManager; -#endif - - -@end - - -@implementation BITHockeyManager - -#pragma mark - Private Class Methods - -- (BOOL)checkValidityOfAppIdentifier:(NSString *)identifier { - BOOL result = NO; - - if (identifier) { - NSCharacterSet *hexSet = [NSCharacterSet characterSetWithCharactersInString:@"0123456789abcdef"]; - NSCharacterSet *inStringSet = [NSCharacterSet characterSetWithCharactersInString:identifier]; - result = ([identifier length] == 32) && ([hexSet isSupersetOfSet:inStringSet]); - } - - return result; -} - -- (void)logInvalidIdentifier:(NSString *)environment { - if (self.appEnvironment != BITEnvironmentAppStore) { - if ([environment isEqualToString:@"liveIdentifier"]) { - BITHockeyLogWarning(@"[HockeySDK] WARNING: The liveIdentifier is invalid! The SDK will be disabled when deployed to the App Store without setting a valid app identifier!"); - } else { - BITHockeyLogError(@"[HockeySDK] ERROR: The %@ is invalid! Please use the HockeyApp app identifier you find on the apps website on HockeyApp! The SDK is disabled!", environment); - } - } -} - -#pragma mark - Public Class Methods - -+ (BITHockeyManager *)sharedHockeyManager { - static BITHockeyManager *sharedInstance = nil; - static dispatch_once_t pred; - - dispatch_once(&pred, ^{ - sharedInstance = [BITHockeyManager alloc]; - sharedInstance = [sharedInstance init]; - }); - - return sharedInstance; -} - -- (instancetype)init { - if ((self = [super init])) { - _serverURL = BITHOCKEYSDK_URL; - _delegate = nil; - _managersInitialized = NO; - - _hockeyAppClient = nil; - -#if HOCKEYSDK_FEATURE_CRASH_REPORTER - _disableCrashManager = NO; -#endif -#if HOCKEYSDK_FEATURE_METRICS - _disableMetricsManager = NO; -#endif -#if HOCKEYSDK_FEATURE_FEEDBACK - _disableFeedbackManager = NO; -#endif -#if HOCKEYSDK_FEATURE_UPDATES - _disableUpdateManager = NO; -#endif -#if HOCKEYSDK_FEATURE_STORE_UPDATES - _enableStoreUpdateManager = NO; -#endif - - _appEnvironment = bit_currentAppEnvironment(); - _startManagerIsInvoked = NO; - _startUpdateManagerIsInvoked = NO; - - _liveIdentifier = nil; - _installString = bit_appAnonID(NO); - _disableInstallTracking = NO; - - [self performSelector:@selector(validateStartManagerIsInvoked) withObject:nil afterDelay:0.0]; - } - return self; -} - -- (void)dealloc { -#if HOCKEYSDK_FEATURE_AUTHENTICATOR - // start Authenticator - if (self.appEnvironment != BITEnvironmentAppStore) { - [self.authenticator removeObserver:self forKeyPath:@"identified"]; - } -#endif -} - - -#pragma mark - Public Instance Methods (Configuration) - -- (void)configureWithIdentifier:(NSString *)appIdentifier { - self.appIdentifier = [appIdentifier copy]; - - [self initializeModules]; -} - -- (void)configureWithIdentifier:(NSString *)appIdentifier delegate:(id)delegate { - self.delegate = delegate; - self.appIdentifier = [appIdentifier copy]; - - [self initializeModules]; -} - -- (void)configureWithBetaIdentifier:(NSString *)betaIdentifier liveIdentifier:(NSString *)liveIdentifier delegate:(id)delegate { - self.delegate = delegate; - - // check the live identifier now, because otherwise invalid identifier would only be logged when the app is already in the store - if (![self checkValidityOfAppIdentifier:liveIdentifier]) { - [self logInvalidIdentifier:@"liveIdentifier"]; - self.liveIdentifier = [liveIdentifier copy]; - } - - if ([self shouldUseLiveIdentifier]) { - self.appIdentifier = [liveIdentifier copy]; - } - else { - self.appIdentifier = [betaIdentifier copy]; - } - - [self initializeModules]; -} - -- (void)startManager { - if (!self.validAppIdentifier) return; - if (self.startManagerIsInvoked) { - BITHockeyLogWarning(@"[HockeySDK] Warning: startManager should only be invoked once! This call is ignored."); - return; - } - - // Fix bug where Application Support directory was encluded from backup - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSURL *appSupportURL = [[fileManager URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] lastObject]; - bit_fixBackupAttributeForURL(appSupportURL); - - if (![self isSetUpOnMainThread]) return; - - if ((self.appEnvironment == BITEnvironmentAppStore) && [self isInstallTrackingDisabled]) { - self.installString = bit_appAnonID(YES); - } - - BITHockeyLogDebug(@"INFO: Starting HockeyManager"); - self.startManagerIsInvoked = YES; - -#if HOCKEYSDK_FEATURE_CRASH_REPORTER - // start CrashManager - if (![self isCrashManagerDisabled]) { - BITHockeyLogDebug(@"INFO: Start CrashManager"); - -#if HOCKEYSDK_FEATURE_AUTHENTICATOR - if (self.authenticator) { - [self.crashManager setInstallationIdentification:[self.authenticator publicInstallationIdentifier]]; - [self.crashManager setInstallationIdentificationType:[self.authenticator identificationType]]; - [self.crashManager setInstallationIdentified:[self.authenticator isIdentified]]; - } -#endif - - [self.crashManager startManager]; - } -#endif /* HOCKEYSDK_FEATURE_CRASH_REPORTER */ - -#if HOCKEYSDK_FEATURE_METRICS - // start MetricsManager - if (!self.isMetricsManagerDisabled) { - BITHockeyLogDebug(@"INFO: Start MetricsManager"); - [self.metricsManager startManager]; - [BITCategoryContainer activateCategory]; - } -#endif /* HOCKEYSDK_FEATURE_METRICS */ - - // App Extensions can only use BITCrashManager and BITMetricsManager, so ignore all others automatically - if (bit_isRunningInAppExtension()) { - return; - } - -#if HOCKEYSDK_FEATURE_STORE_UPDATES - // start StoreUpdateManager - if ([self isStoreUpdateManagerEnabled]) { - BITHockeyLogDebug(@"INFO: Start StoreUpdateManager"); - if (self.serverURL) { - [self.storeUpdateManager setServerURL:self.serverURL]; - } - [self.storeUpdateManager performSelector:@selector(startManager) withObject:nil afterDelay:0.5]; - } -#endif /* HOCKEYSDK_FEATURE_STORE_UPDATES */ - -#if HOCKEYSDK_FEATURE_FEEDBACK - // start FeedbackManager - if (![self isFeedbackManagerDisabled]) { - BITHockeyLogDebug(@"INFO: Start FeedbackManager"); - if (self.serverURL) { - [self.feedbackManager setServerURL:self.serverURL]; - } - [self.feedbackManager performSelector:@selector(startManager) withObject:nil afterDelay:1.0]; - } -#endif /* HOCKEYSDK_FEATURE_FEEDBACK */ - -#if HOCKEYSDK_FEATURE_AUTHENTICATOR - // start Authenticator - if (self.appEnvironment != BITEnvironmentAppStore) { - // hook into manager with kvo! - [self.authenticator addObserver:self forKeyPath:@"identified" options:0 context:nil]; - - BITHockeyLogDebug(@"INFO: Start Authenticator"); - if (self.serverURL) { - [self.authenticator setServerURL:self.serverURL]; - } - [self.authenticator performSelector:@selector(startManager) withObject:nil afterDelay:0.5]; - } -#endif /* HOCKEYSDK_FEATURE_AUTHENTICATOR */ - -#if HOCKEYSDK_FEATURE_UPDATES - BOOL isIdentified = NO; - -#if HOCKEYSDK_FEATURE_AUTHENTICATOR - if (self.appEnvironment != BITEnvironmentAppStore) - isIdentified = [self.authenticator isIdentified]; -#endif /* HOCKEYSDK_FEATURE_AUTHENTICATOR */ - - // Setup UpdateManager - if (![self isUpdateManagerDisabled] && isIdentified) { - [self invokeStartUpdateManager]; - } -#endif /* HOCKEYSDK_FEATURE_UPDATES */ -} - -#if HOCKEYSDK_FEATURE_UPDATES -- (void)setDisableUpdateManager:(BOOL)disableUpdateManager { - if (self.updateManager) { - [self.updateManager setDisableUpdateManager:disableUpdateManager]; - } - _disableUpdateManager = disableUpdateManager; -} -#endif /* HOCKEYSDK_FEATURE_UPDATES */ - - -#if HOCKEYSDK_FEATURE_STORE_UPDATES -- (void)setEnableStoreUpdateManager:(BOOL)enableStoreUpdateManager { - if (self.storeUpdateManager) { - [self.storeUpdateManager setEnableStoreUpdateManager:enableStoreUpdateManager]; - } - _enableStoreUpdateManager = enableStoreUpdateManager; -} -#endif /* HOCKEYSDK_FEATURE_STORE_UPDATES */ - - -#if HOCKEYSDK_FEATURE_FEEDBACK -- (void)setDisableFeedbackManager:(BOOL)disableFeedbackManager { - if (self.feedbackManager) { - [self.feedbackManager setDisableFeedbackManager:disableFeedbackManager]; - } - _disableFeedbackManager = disableFeedbackManager; -} -#endif /* HOCKEYSDK_FEATURE_FEEDBACK */ - -#if HOCKEYSDK_FEATURE_METRICS -- (void)setDisableMetricsManager:(BOOL)disableMetricsManager { - if (self.metricsManager) { - self.metricsManager.disabled = disableMetricsManager; - } - _disableMetricsManager = disableMetricsManager; -} -#endif /* HOCKEYSDK_FEATURE_METRICS */ - -- (void)setServerURL:(NSString *)aServerURL { - // ensure url ends with a trailing slash - if (![aServerURL hasSuffix:@"/"]) { - aServerURL = [NSString stringWithFormat:@"%@/", aServerURL]; - } - - if (self.serverURL != aServerURL) { - _serverURL = [aServerURL copy]; - - if (self.hockeyAppClient) { - self.hockeyAppClient.baseURL = [NSURL URLWithString:self.serverURL ?: BITHOCKEYSDK_URL]; - } - } -} - - -- (void)setDelegate:(id)delegate { - if (self.appEnvironment != BITEnvironmentAppStore) { - if (self.startManagerIsInvoked) { - BITHockeyLogError(@"[HockeySDK] ERROR: The `delegate` property has to be set before calling [[BITHockeyManager sharedHockeyManager] startManager] !"); - } - } - - if (_delegate != delegate) { - _delegate = delegate; - id currentDelegate = _delegate; - -#if HOCKEYSDK_FEATURE_CRASH_REPORTER - if (self.crashManager) { - self.crashManager.delegate = currentDelegate; - } -#endif /* HOCKEYSDK_FEATURE_CRASH_REPORTER */ - -#if HOCKEYSDK_FEATURE_UPDATES - if (self.updateManager) { - self.updateManager.delegate = currentDelegate; - } -#endif /* HOCKEYSDK_FEATURE_UPDATES */ - -#if HOCKEYSDK_FEATURE_FEEDBACK - if (self.feedbackManager) { - self.feedbackManager.delegate = currentDelegate; - } -#endif /* HOCKEYSDK_FEATURE_FEEDBACK */ - -#if HOCKEYSDK_FEATURE_STORE_UPDATES - if (self.storeUpdateManager) { - self.storeUpdateManager.delegate = currentDelegate; - } -#endif /* HOCKEYSDK_FEATURE_STORE_UPDATES */ - -#if HOCKEYSDK_FEATURE_AUTHENTICATOR - if (self.authenticator) { - self.authenticator.delegate = currentDelegate; - } -#endif /* HOCKEYSDK_FEATURE_AUTHENTICATOR */ - } -} - -- (void)setDebugLogEnabled:(BOOL)debugLogEnabled { - _debugLogEnabled = debugLogEnabled; - if (debugLogEnabled) { - self.logLevel = BITLogLevelDebug; - } else { - self.logLevel = BITLogLevelWarning; - } -} - -- (BITLogLevel)logLevel { - return BITHockeyLogger.currentLogLevel; -} - -- (void)setLogLevel:(BITLogLevel)logLevel { - BITHockeyLogger.currentLogLevel = logLevel; -} - -- (void)setLogHandler:(BITLogHandler)logHandler { - [BITHockeyLogger setLogHandler:logHandler]; -} - -- (void)modifyKeychainUserValue:(NSString *)value forKey:(NSString *)key { - NSError *error = nil; - BOOL success = YES; - NSString *updateType = @"update"; - - if (value) { - success = [BITKeychainUtils storeUsername:key - andPassword:value - forServiceName:bit_keychainHockeySDKServiceName() - updateExisting:YES - accessibility:kSecAttrAccessibleAlwaysThisDeviceOnly - error:&error]; - } else { - updateType = @"delete"; - if ([BITKeychainUtils getPasswordForUsername:key - andServiceName:bit_keychainHockeySDKServiceName() - error:&error]) { - success = [BITKeychainUtils deleteItemForUsername:key - andServiceName:bit_keychainHockeySDKServiceName() - error:&error]; - } - } - - if (!success) { - NSString *errorDescription = [error description] ?: @""; - BITHockeyLogError(@"ERROR: Couldn't %@ key %@ in the keychain. %@", updateType, key, errorDescription); - } -} - -- (void)setUserID:(NSString *)userID { - // always set it, since nil value will trigger removal of the keychain entry - _userID = userID; - - [self modifyKeychainUserValue:userID forKey:kBITHockeyMetaUserID]; -} - -- (void)setUserName:(NSString *)userName { - // always set it, since nil value will trigger removal of the keychain entry - _userName = userName; - - [self modifyKeychainUserValue:userName forKey:kBITHockeyMetaUserName]; -} - -- (void)setUserEmail:(NSString *)userEmail { - // always set it, since nil value will trigger removal of the keychain entry - _userEmail = userEmail; - - [self modifyKeychainUserValue:userEmail forKey:kBITHockeyMetaUserEmail]; -} - -- (void)testIdentifier { - if (!self.appIdentifier || (self.appEnvironment == BITEnvironmentAppStore)) { - return; - } - - NSDate *now = [NSDate date]; - NSString *timeString = [NSString stringWithFormat:@"%.0f", [now timeIntervalSince1970]]; - [self pingServerForIntegrationStartWorkflowWithTimeString:timeString appIdentifier:self.appIdentifier]; - - if (self.liveIdentifier) { - [self pingServerForIntegrationStartWorkflowWithTimeString:timeString appIdentifier:self.liveIdentifier]; - } -} - - -- (NSString *)version { - return (NSString *)[NSString stringWithUTF8String:BITHOCKEY_C_VERSION]; -} - -- (NSString *)build { - return (NSString *)[NSString stringWithUTF8String:BITHOCKEY_C_BUILD]; -} - - -#pragma mark - KVO - -#if HOCKEYSDK_FEATURE_UPDATES -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *) __unused change context:(void *) __unused context { - if ([keyPath isEqualToString:@"identified"] && - [object valueForKey:@"isIdentified"] ) { - if (self.appEnvironment != BITEnvironmentAppStore) { - BOOL identified = [(NSNumber *)[object valueForKey:@"isIdentified"] boolValue]; - if (identified && ![self isUpdateManagerDisabled]) { - [self invokeStartUpdateManager]; - } - } - } -} -#endif /* HOCKEYSDK_FEATURE_UPDATES */ - - -#pragma mark - Private Instance Methods - -- (BITHockeyAppClient *)hockeyAppClient { - if (!_hockeyAppClient) { - _hockeyAppClient = [[BITHockeyAppClient alloc] initWithBaseURL:[NSURL URLWithString:self.serverURL]]; - } - return _hockeyAppClient; -} - -- (NSString *)integrationFlowTimeString { - NSString *timeString = [[NSBundle mainBundle] objectForInfoDictionaryKey:BITHOCKEY_INTEGRATIONFLOW_TIMESTAMP]; - - return timeString; -} - -- (BOOL)integrationFlowStartedWithTimeString:(NSString *)timeString { - if (timeString == nil || (self.appEnvironment == BITEnvironmentAppStore)) { - return NO; - } - - NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; - NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; - [dateFormatter setLocale:enUSPOSIXLocale]; - [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZ"]; - NSDate *integrationFlowStartDate = [dateFormatter dateFromString:timeString]; - - if (integrationFlowStartDate && [integrationFlowStartDate timeIntervalSince1970] > [[NSDate date] timeIntervalSince1970] - (60 * 10) ) { - return YES; - } - - return NO; -} - -- (void)pingServerForIntegrationStartWorkflowWithTimeString:(NSString *)timeString appIdentifier:(NSString *)appIdentifier { - if (!appIdentifier || (self.appEnvironment == BITEnvironmentAppStore)) { - return; - } - - NSString *integrationPath = [NSString stringWithFormat:@"api/3/apps/%@/integration", bit_encodeAppIdentifier(appIdentifier)]; - - BITHockeyLogDebug(@"INFO: Sending integration workflow ping to %@", integrationPath); - - NSDictionary *params = @{@"timestamp": timeString, - @"sdk": BITHOCKEY_NAME, - @"sdk_version": BITHOCKEY_VERSION, - @"bundle_version": (id)[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"] - }; - - NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; - __block NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration]; - NSURLRequest *request = [[self hockeyAppClient] requestWithMethod:@"POST" path:integrationPath parameters:params]; - NSURLSessionDataTask *task = [session dataTaskWithRequest:request - completionHandler: ^(NSData * __unused data, NSURLResponse *response, NSError * __unused error) { - [session finishTasksAndInvalidate]; - - NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*) response; - [self logPingMessageForStatusCode:httpResponse.statusCode]; - }]; - [task resume]; - -} - -- (void)logPingMessageForStatusCode:(NSInteger)statusCode { - switch (statusCode) { - case 400: - BITHockeyLogError(@"ERROR: App ID not found"); - break; - case 201: - BITHockeyLogDebug(@"INFO: Ping accepted."); - break; - case 200: - BITHockeyLogDebug(@"INFO: Ping accepted. Server already knows."); - break; - default: - BITHockeyLogError(@"ERROR: Unknown error"); - break; - } -} - -- (void)validateStartManagerIsInvoked { - if (self.validAppIdentifier && (self.appEnvironment != BITEnvironmentAppStore)) { - if (!self.startManagerIsInvoked) { - BITHockeyLogError(@"[HockeySDK] ERROR: You did not call [[BITHockeyManager sharedHockeyManager] startManager] to startup the HockeySDK! Please do so after setting up all properties. The SDK is NOT running."); - } - } -} - -#if HOCKEYSDK_FEATURE_UPDATES -- (void)invokeStartUpdateManager { - if (self.startUpdateManagerIsInvoked) return; - - self.startUpdateManagerIsInvoked = YES; - BITHockeyLogDebug(@"INFO: Start UpdateManager"); - if (self.serverURL) { - [self.updateManager setServerURL:self.serverURL]; - } -#if HOCKEYSDK_FEATURE_AUTHENTICATOR - if (self.authenticator) { - [self.updateManager setInstallationIdentification:[self.authenticator installationIdentifier]]; - [self.updateManager setInstallationIdentificationType:[self.authenticator installationIdentifierParameterString]]; - [self.updateManager setInstallationIdentified:[self.authenticator isIdentified]]; - } -#endif - [self.updateManager performSelector:@selector(startManager) withObject:nil afterDelay:0.5]; -} -#endif /* HOCKEYSDK_FEATURE_UPDATES */ - -- (BOOL)isSetUpOnMainThread { - NSString *errorString = @"ERROR: HockeySDK has to be setup on the main thread!"; - - if (!NSThread.isMainThread) { - if (self.appEnvironment == BITEnvironmentAppStore) { - BITHockeyLogError(@"%@", errorString); - } else { - BITHockeyLogError(@"%@", errorString); - NSAssert(NSThread.isMainThread, errorString); - } - - return NO; - } - - return YES; -} - -- (BOOL)shouldUseLiveIdentifier { - BOOL delegateResult = NO; - id currentDelegate = self.delegate; - if ([currentDelegate respondsToSelector:@selector(shouldUseLiveIdentifierForHockeyManager:)]) { - delegateResult = [currentDelegate shouldUseLiveIdentifierForHockeyManager:self]; - } - - return (delegateResult) || (self.appEnvironment == BITEnvironmentAppStore); -} - -- (void)initializeModules { - if (self.managersInitialized) { - BITHockeyLogWarning(@"[HockeySDK] Warning: The SDK should only be initialized once! This call is ignored."); - return; - } - - self.validAppIdentifier = [self checkValidityOfAppIdentifier:self.appIdentifier]; - - if (![self isSetUpOnMainThread]) return; - - self.startManagerIsInvoked = NO; - - if (self.validAppIdentifier) { - id currentDelegate = self.delegate; - -#if HOCKEYSDK_FEATURE_CRASH_REPORTER - BITHockeyLogDebug(@"INFO: Setup CrashManager"); - self.crashManager = [[BITCrashManager alloc] initWithAppIdentifier:self.appIdentifier - appEnvironment:self.appEnvironment - hockeyAppClient:[self hockeyAppClient]]; - self.crashManager.delegate = currentDelegate; -#endif /* HOCKEYSDK_FEATURE_CRASH_REPORTER */ - -#if HOCKEYSDK_FEATURE_UPDATES - BITHockeyLogDebug(@"INFO: Setup UpdateManager"); - self.updateManager = [[BITUpdateManager alloc] initWithAppIdentifier:self.appIdentifier appEnvironment:self.appEnvironment]; - self.updateManager.delegate = currentDelegate; -#endif /* HOCKEYSDK_FEATURE_UPDATES */ - -#if HOCKEYSDK_FEATURE_STORE_UPDATES - BITHockeyLogDebug(@"INFO: Setup StoreUpdateManager"); - self.storeUpdateManager = [[BITStoreUpdateManager alloc] initWithAppIdentifier:self.appIdentifier appEnvironment:self.appEnvironment]; - self.storeUpdateManager.delegate = currentDelegate; -#endif /* HOCKEYSDK_FEATURE_STORE_UPDATES */ - -#if HOCKEYSDK_FEATURE_FEEDBACK - BITHockeyLogDebug(@"INFO: Setup FeedbackManager"); - self.feedbackManager = [[BITFeedbackManager alloc] initWithAppIdentifier:self.appIdentifier appEnvironment:self.appEnvironment]; - self.feedbackManager.delegate = currentDelegate; -#endif /* HOCKEYSDK_FEATURE_FEEDBACK */ - -#if HOCKEYSDK_FEATURE_AUTHENTICATOR - BITHockeyLogDebug(@"INFO: Setup Authenticator"); - self.authenticator = [[BITAuthenticator alloc] initWithAppIdentifier:self.appIdentifier appEnvironment:self.appEnvironment]; - self.authenticator.hockeyAppClient = [self hockeyAppClient]; - self.authenticator.delegate = currentDelegate; -#endif /* HOCKEYSDK_FEATURE_AUTHENTICATOR */ - -#if HOCKEYSDK_FEATURE_METRICS - BITHockeyLogDebug(@"INFO: Setup MetricsManager"); - NSString *iKey = bit_appIdentifierToGuid(self.appIdentifier); - self.metricsManager = [[BITMetricsManager alloc] initWithAppIdentifier:iKey appEnvironment:self.appEnvironment]; -#endif /* HOCKEYSDK_FEATURE_METRICS */ - - if (self.appEnvironment != BITEnvironmentAppStore) { - NSString *integrationFlowTime = [self integrationFlowTimeString]; - if (integrationFlowTime && [self integrationFlowStartedWithTimeString:integrationFlowTime]) { - [self pingServerForIntegrationStartWorkflowWithTimeString:integrationFlowTime appIdentifier:self.appIdentifier]; - } - } - self.managersInitialized = YES; - } else { - [self logInvalidIdentifier:@"app identifier"]; - } -} - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITHockeyManagerDelegate.h b/submodules/HockeySDK-iOS/Classes/BITHockeyManagerDelegate.h deleted file mode 100644 index a95b546890..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITHockeyManagerDelegate.h +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import -#import "HockeySDKFeatureConfig.h" - -#if HOCKEYSDK_FEATURE_CRASH_REPORTER -#import "BITCrashManagerDelegate.h" -#endif - -#if HOCKEYSDK_FEATURE_UPDATES -#import "BITUpdateManagerDelegate.h" -#endif - -#if HOCKEYSDK_FEATURE_FEEDBACK -//#import "BITFeedbackManagerDelegate.h" -#endif - -#if HOCKEYSDK_FEATURE_AUTHENTICATOR -//#import "BITAuthenticator.h" -#endif - -@class BITHockeyManager; -@class BITHockeyBaseManager; - -/** - The `BITHockeyManagerDelegate` formal protocol defines methods further configuring - the behaviour of `BITHockeyManager`, as well as the delegate of the modules it manages. - */ - -@protocol BITHockeyManagerDelegate - -@optional - - -///----------------------------------------------------------------------------- -/// @name App Identifier usage -///----------------------------------------------------------------------------- - -/** - Implement to force the usage of the live identifier - - This is useful if you are e.g. distributing an enterprise app inside your company - and want to use the `liveIdentifier` for that even though it is not running from - the App Store. - - Example: - - - (BOOL)shouldUseLiveIdentifierForHockeyManager:(BITHockeyManager *)hockeyManager { - #ifdef (CONFIGURATION_AppStore) - return YES; - #endif - return NO; - } - - @param hockeyManager BITHockeyManager instance - */ -- (BOOL)shouldUseLiveIdentifierForHockeyManager:(BITHockeyManager *)hockeyManager; - - -///----------------------------------------------------------------------------- -/// @name UI presentation -///----------------------------------------------------------------------------- - - -// optional parent view controller for the feedback screen when invoked via the alert view, default is the root UIWindow instance -/** - Return a custom parent view controller for presenting modal sheets - - By default the SDK is using the root UIWindow instance to present any required - view controllers. Overwrite this if this doesn't result in a satisfying - behavior or if you want to define any other parent view controller. - - @param hockeyManager The `BITHockeyManager` HockeyManager instance invoking this delegate - @param componentManager The `BITHockeyBaseManager` component instance invoking this delegate, can be `BITCrashManager` or `BITFeedbackManager` - */ -- (UIViewController *)viewControllerForHockeyManager:(BITHockeyManager *)hockeyManager componentManager:(BITHockeyBaseManager *)componentManager; - - -///----------------------------------------------------------------------------- -/// @name Additional meta data -///----------------------------------------------------------------------------- - - -/** Return the userid that should used in the SDK components - - Right now this is used by the `BITCrashManager` to attach to a crash report. - `BITFeedbackManager` uses it too for assigning the user to a discussion thread. - - In addition, if this returns not nil for `BITFeedbackManager` the user will - not be asked for any user details by the component, including userName or userEmail. - - You can find out the component requesting the userID like this: - - - (NSString *)userIDForHockeyManager:(BITHockeyManager *)hockeyManager componentManager:(BITHockeyBaseManager *)componentManager { - if (componentManager == hockeyManager.feedbackManager) { - return UserIDForFeedback; - } else if (componentManager == hockeyManager.crashManager) { - return UserIDForCrashReports; - } else { - return nil; - } - } - - For crash reports, this delegate is invoked on the startup after the crash! - - Alternatively you can also use `[BITHockeyManager userID]` which will cache the value in the keychain. - - @warning When returning a non nil value for the `BITCrashManager` component, crash reports - are not anonymous any more and the crash alerts will not show the word "anonymous"! - - @param hockeyManager The `BITHockeyManager` HockeyManager instance invoking this delegate - @param componentManager The `BITHockeyBaseManager` component instance invoking this delegate, can be `BITCrashManager` or `BITFeedbackManager` - @see userNameForHockeyManager:componentManager: - @see userEmailForHockeyManager:componentManager: - @see [BITHockeyManager userID] - */ -- (NSString *)userIDForHockeyManager:(BITHockeyManager *)hockeyManager componentManager:(BITHockeyBaseManager *)componentManager; - - -/** Return the user name that should used in the SDK components - - Right now this is used by the `BITCrashManager` to attach to a crash report. - `BITFeedbackManager` uses it too for assigning the user to a discussion thread. - - In addition, if this returns not nil for `BITFeedbackManager` the user will - not be asked for any user details by the component, including userName or userEmail. - - You can find out the component requesting the user name like this: - - - (NSString *)userNameForHockeyManager:(BITHockeyManager *)hockeyManager componentManager:(BITHockeyBaseManager *)componentManager { - if (componentManager == hockeyManager.feedbackManager) { - return UserNameForFeedback; - } else if (componentManager == hockeyManager.crashManager) { - return UserNameForCrashReports; - } else { - return nil; - } - } - - For crash reports, this delegate is invoked on the startup after the crash! - - Alternatively you can also use `[BITHockeyManager userName]` which will cache the value in the keychain. - - @warning When returning a non nil value for the `BITCrashManager` component, crash reports - are not anonymous any more and the crash alerts will not show the word "anonymous"! - - @param hockeyManager The `BITHockeyManager` HockeyManager instance invoking this delegate - @param componentManager The `BITHockeyBaseManager` component instance invoking this delegate, can be `BITCrashManager` or `BITFeedbackManager` - @see userIDForHockeyManager:componentManager: - @see userEmailForHockeyManager:componentManager: - @see [BITHockeyManager userName] - */ -- (NSString *)userNameForHockeyManager:(BITHockeyManager *)hockeyManager componentManager:(BITHockeyBaseManager *)componentManager; - - -/** Return the users email address that should used in the SDK components - - Right now this is used by the `BITCrashManager` to attach to a crash report. - `BITFeedbackManager` uses it too for assigning the user to a discussion thread. - - In addition, if this returns not nil for `BITFeedbackManager` the user will - not be asked for any user details by the component, including userName or userEmail. - - You can find out the component requesting the user email like this: - - - (NSString *)userEmailForHockeyManager:(BITHockeyManager *)hockeyManager componentManager:(BITHockeyBaseManager *)componentManager { - if (componentManager == hockeyManager.feedbackManager) { - return UserEmailForFeedback; - } else if (componentManager == hockeyManager.crashManager) { - return UserEmailForCrashReports; - } else { - return nil; - } - } - - For crash reports, this delegate is invoked on the startup after the crash! - - Alternatively you can also use `[BITHockeyManager userEmail]` which will cache the value in the keychain. - - @warning When returning a non nil value for the `BITCrashManager` component, crash reports - are not anonymous any more and the crash alerts will not show the word "anonymous"! - - @param hockeyManager The `BITHockeyManager` HockeyManager instance invoking this delegate - @param componentManager The `BITHockeyBaseManager` component instance invoking this delegate, can be `BITCrashManager` or `BITFeedbackManager` - @see userIDForHockeyManager:componentManager: - @see userNameForHockeyManager:componentManager: - @see [BITHockeyManager userEmail] - */ -- (NSString *)userEmailForHockeyManager:(BITHockeyManager *)hockeyManager componentManager:(BITHockeyBaseManager *)componentManager; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITImageAnnotation.h b/submodules/HockeySDK-iOS/Classes/BITImageAnnotation.h deleted file mode 100644 index c6788e41f8..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITImageAnnotation.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Author: Moritz Haarmann - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import - -@interface BITImageAnnotation : UIView - -@property (nonatomic, getter=isSelected) BOOL selected; -@property (nonatomic) CGSize movedDelta; -@property (nonatomic, weak) UIImage *sourceImage; -@property (nonatomic) CGRect imageFrame; - -- (BOOL)resizable; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITImageAnnotation.m b/submodules/HockeySDK-iOS/Classes/BITImageAnnotation.m deleted file mode 100644 index d7bc699471..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITImageAnnotation.m +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Author: Moritz Haarmann - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_FEEDBACK - -#import "BITImageAnnotation.h" - -@implementation BITImageAnnotation - --(BOOL)resizable { - return NO; -} - -@end - -#endif diff --git a/submodules/HockeySDK-iOS/Classes/BITImageAnnotationViewController.h b/submodules/HockeySDK-iOS/Classes/BITImageAnnotationViewController.h deleted file mode 100644 index 026968ceb3..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITImageAnnotationViewController.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Author: Moritz Haarmann - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import - -@class BITImageAnnotationViewController; - -@protocol BITImageAnnotationDelegate - -- (void)annotationControllerDidCancel:(BITImageAnnotationViewController *)annotationController; -- (void)annotationController:(BITImageAnnotationViewController *)annotationController didFinishWithImage:(UIImage *)image; - -@end - -@interface BITImageAnnotationViewController : UIViewController - -@property (nonatomic, strong) UIImage *image; -@property (nonatomic, weak) id delegate; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITImageAnnotationViewController.m b/submodules/HockeySDK-iOS/Classes/BITImageAnnotationViewController.m deleted file mode 100644 index f8d95972b3..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITImageAnnotationViewController.m +++ /dev/null @@ -1,415 +0,0 @@ -/* - * Author: Moritz Haarmann - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_FEEDBACK - -#import "BITImageAnnotationViewController.h" -#import "BITImageAnnotation.h" -#import "BITRectangleImageAnnotation.h" -#import "BITArrowImageAnnotation.h" -#import "BITBlurImageAnnotation.h" -#import "BITHockeyHelper.h" -#import "HockeySDKPrivate.h" - -typedef NS_ENUM(NSInteger, BITImageAnnotationViewControllerInteractionMode) { - BITImageAnnotationViewControllerInteractionModeNone, - BITImageAnnotationViewControllerInteractionModeDraw, - BITImageAnnotationViewControllerInteractionModeMove -}; - -@interface BITImageAnnotationViewController () - -@property (nonatomic, strong) UIImageView *imageView; -@property (nonatomic, strong) UISegmentedControl *editingControls; -@property (nonatomic, strong) NSMutableArray *objects; - -@property (nonatomic, strong) UITapGestureRecognizer *tapRecognizer; -@property (nonatomic, strong) UIPanGestureRecognizer *panRecognizer; -@property (nonatomic, strong) UIPinchGestureRecognizer *pinchRecognizer; - -@property (nonatomic) CGFloat scaleFactor; - -@property (nonatomic) CGPoint panStart; -@property (nonatomic,strong) BITImageAnnotation *currentAnnotation; - -@property (nonatomic) BITImageAnnotationViewControllerInteractionMode currentInteraction; - -@property (nonatomic) CGRect pinchStartingFrame; - -@end - -@implementation BITImageAnnotationViewController - -#pragma mark - UIViewController - -- (void)viewDidLoad { - [super viewDidLoad]; - - self.view.backgroundColor = [UIColor groupTableViewBackgroundColor]; - - NSArray *icons = @[@"Arrow.png",@"Rectangle.png", @"Blur.png"]; - - self.editingControls = [[UISegmentedControl alloc] initWithItems:@[@"Rectangle", @"Arrow", @"Blur"]]; - int i=0; - for (NSString *imageName in icons){ - [self.editingControls setImage:bit_imageNamed(imageName, BITHOCKEYSDK_BUNDLE) forSegmentAtIndex:i++]; - } - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [self.editingControls setSegmentedControlStyle:UISegmentedControlStyleBar]; -#pragma clang diagnostic pop - - self.navigationItem.titleView = self.editingControls; - - self.objects = [NSMutableArray new]; - - [self.editingControls addTarget:self action:@selector(editingAction:) forControlEvents:UIControlEventTouchUpInside]; - [self.editingControls setSelectedSegmentIndex:0]; - - self.imageView = [[UIImageView alloc] initWithFrame:self.view.bounds]; - - self.imageView.clipsToBounds = YES; - - self.imageView.image = self.image; - self.imageView.contentMode = UIViewContentModeScaleToFill; - - self.view.frame = UIScreen.mainScreen.bounds; - - [self.view addSubview:self.imageView]; - // Erm. - self.imageView.frame = [UIScreen mainScreen].bounds; - - self.panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panned:)]; - self.pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinched:)]; - self.tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)]; - - [self.imageView addGestureRecognizer:self.pinchRecognizer]; - [self.imageView addGestureRecognizer:self.panRecognizer]; - [self.view addGestureRecognizer:self.tapRecognizer]; - - self.imageView.userInteractionEnabled = YES; - - self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc ] initWithImage:bit_imageNamed(@"Cancel.png", BITHOCKEYSDK_BUNDLE) landscapeImagePhone:bit_imageNamed(@"Cancel.png", BITHOCKEYSDK_BUNDLE) style:UIBarButtonItemStylePlain target:self action:@selector(discard:)]; - self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc ] initWithImage:bit_imageNamed(@"Ok.png", BITHOCKEYSDK_BUNDLE) landscapeImagePhone:bit_imageNamed(@"Ok.png", BITHOCKEYSDK_BUNDLE) style:UIBarButtonItemStylePlain target:self action:@selector(save:)]; - - self.view.autoresizesSubviews = NO; -} - - -- (void)viewWillAppear:(BOOL)animated { - [super viewWillAppear:animated]; - - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil]; - - [self fitImageViewFrame]; - -} - -- (void)viewWillDisappear:(BOOL)animated { - [super viewWillDisappear:animated]; - - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil]; -} - -- (BOOL)prefersStatusBarHidden { - return self.navigationController.navigationBarHidden || self.navigationController.navigationBar.alpha == 0; -} - -- (void)orientationDidChange:(NSNotification *) __unused notification { - [self fitImageViewFrame]; -} - - -- (void)fitImageViewFrame { - - CGSize size = [UIScreen mainScreen].bounds.size; - if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation) && size.height > size.width){ - size = CGSizeMake(size.height, size.width); - } - - CGFloat heightScaleFactor = size.height / self.image.size.height; - CGFloat widthScaleFactor = size.width / self.image.size.width; - - CGFloat factor = MIN(heightScaleFactor, widthScaleFactor); - self.scaleFactor = factor; - CGSize scaledImageSize = CGSizeMake(self.image.size.width * factor, self.image.size.height * factor); - - CGRect baseFrame = CGRectMake(self.view.frame.size.width/2 - scaledImageSize.width/2, self.view.frame.size.height/2 - scaledImageSize.height/2, scaledImageSize.width, scaledImageSize.height); - - self.imageView.frame = baseFrame; -} - -- (void)editingAction:(id) __unused sender { - -} - -- (BITImageAnnotation *)annotationForCurrentMode { - if (self.editingControls.selectedSegmentIndex == 0){ - return [[BITArrowImageAnnotation alloc] initWithFrame:CGRectZero]; - } else if(self.editingControls.selectedSegmentIndex==1){ - return [[BITRectangleImageAnnotation alloc] initWithFrame:CGRectZero]; - } else { - return [[BITBlurImageAnnotation alloc] initWithFrame:CGRectZero]; - } -} - -#pragma mark - Actions - -- (void)discard:(id) __unused sender { - [self.delegate annotationControllerDidCancel:self]; - [self dismissViewControllerAnimated:YES completion:nil]; -} - -- (void)save:(id) __unused sender { - UIImage *image = [self extractImage]; - [self.delegate annotationController:self didFinishWithImage:image]; - [self dismissViewControllerAnimated:YES completion:nil]; -} - -- (UIImage *)extractImage { - UIGraphicsBeginImageContextWithOptions(self.image.size, YES, 0.0); - CGContextRef ctx = UIGraphicsGetCurrentContext(); - [self.image drawInRect:CGRectMake(0, 0, self.image.size.width, self.image.size.height)]; - CGContextScaleCTM(ctx,((CGFloat)1.0)/self.scaleFactor,((CGFloat)1.0)/self.scaleFactor); - - // Drawing all the annotations onto the final image. - for (BITImageAnnotation *annotation in self.objects){ - CGContextTranslateCTM(ctx, annotation.frame.origin.x, annotation.frame.origin.y); - [annotation.layer renderInContext:ctx]; - CGContextTranslateCTM(ctx,-1 * annotation.frame.origin.x,-1 * annotation.frame.origin.y); - } - - UIImage *renderedImageOfMyself = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return renderedImageOfMyself; -} - -#pragma mark - UIGestureRecognizers - -- (void)panned:(UIPanGestureRecognizer *)gestureRecognizer { - BITImageAnnotation *annotationAtLocation = (BITImageAnnotation *)[self.view hitTest:[gestureRecognizer locationInView:self.view] withEvent:nil]; - - if (![annotationAtLocation isKindOfClass:[BITImageAnnotation class]]){ - annotationAtLocation = nil; - } - - // determine the interaction mode if none is set so far. - - if (self.currentInteraction == BITImageAnnotationViewControllerInteractionModeNone){ - if (annotationAtLocation){ - self.currentInteraction = BITImageAnnotationViewControllerInteractionModeMove; - } else if ([self canDrawNewAnnotation]){ - self.currentInteraction = BITImageAnnotationViewControllerInteractionModeDraw; - } - } - - if (self.currentInteraction == BITImageAnnotationViewControllerInteractionModeNone){ - return; - } - - - if (self.currentInteraction == BITImageAnnotationViewControllerInteractionModeDraw){ - if (gestureRecognizer.state == UIGestureRecognizerStateBegan){ - self.currentAnnotation = [self annotationForCurrentMode]; - [self.objects addObject:self.currentAnnotation]; - self.currentAnnotation.sourceImage = self.image; - - if (self.imageView.subviews.count > 0 && [self.currentAnnotation isKindOfClass:[BITBlurImageAnnotation class]]){ - [self.imageView insertSubview:self.currentAnnotation belowSubview:[self firstAnnotationThatIsNotBlur]]; - } else { - [self.imageView addSubview:self.currentAnnotation]; - } - - self.panStart = [gestureRecognizer locationInView:self.imageView]; - - } else if (gestureRecognizer.state == UIGestureRecognizerStateChanged){ - CGPoint bla = [gestureRecognizer locationInView:self.imageView]; - self.currentAnnotation.frame = CGRectMake(self.panStart.x, self.panStart.y, bla.x - self.panStart.x, bla.y - self.panStart.y); - self.currentAnnotation.movedDelta = CGSizeMake(bla.x - self.panStart.x, bla.y - self.panStart.y); - self.currentAnnotation.imageFrame = [self.view convertRect:self.imageView.frame toView:self.currentAnnotation]; - [self.currentAnnotation setNeedsLayout]; - [self.currentAnnotation layoutIfNeeded]; - } else { - [self.currentAnnotation setSelected:NO]; - self.currentAnnotation = nil; - self.currentInteraction = BITImageAnnotationViewControllerInteractionModeNone; - } - } else if (self.currentInteraction == BITImageAnnotationViewControllerInteractionModeMove){ - if (gestureRecognizer.state == UIGestureRecognizerStateBegan){ - // find and possibly move an existing annotation. - - - if ([self.objects indexOfObject:annotationAtLocation] != NSNotFound){ - self.currentAnnotation = annotationAtLocation; - [annotationAtLocation setSelected:YES]; - } - - - } else if (gestureRecognizer.state == UIGestureRecognizerStateChanged && self.currentAnnotation){ - CGPoint delta = [gestureRecognizer translationInView:self.view]; - - CGRect annotationFrame = self.currentAnnotation.frame; - annotationFrame.origin.x += delta.x; - annotationFrame.origin.y += delta.y; - self.currentAnnotation.frame = annotationFrame; - self.currentAnnotation.imageFrame = [self.view convertRect:self.imageView.frame toView:self.currentAnnotation]; - - [self.currentAnnotation setNeedsLayout]; - [self.currentAnnotation layoutIfNeeded]; - - [gestureRecognizer setTranslation:CGPointZero inView:self.view]; - - } else { - [self.currentAnnotation setSelected:NO]; - self.currentAnnotation = nil; - self.currentInteraction = BITImageAnnotationViewControllerInteractionModeNone; - } - } -} - -- (void)pinched:(UIPinchGestureRecognizer *)gestureRecognizer { - if (gestureRecognizer.state == UIGestureRecognizerStateBegan){ - // try to figure out which view we are talking about. - BITImageAnnotation *candidate = nil; - BOOL validView = YES; - - for (uint i = 0; i < gestureRecognizer.numberOfTouches; i++){ - BITImageAnnotation *newCandidate = (BITImageAnnotation *)[self.view hitTest:[gestureRecognizer locationOfTouch:i inView:self.view] withEvent:nil]; - - if (![newCandidate isKindOfClass:[BITImageAnnotation class]]){ - newCandidate = nil; - } - - if (candidate == nil){ - candidate = newCandidate; - } else if (candidate != newCandidate){ - validView = NO; - break; - } - } - - if (validView && [candidate resizable]){ - self.currentAnnotation = candidate; - self.pinchStartingFrame = self.currentAnnotation.frame; - [self.currentAnnotation setSelected:YES]; - } - - } else if (gestureRecognizer.state == UIGestureRecognizerStateChanged && self.currentAnnotation && gestureRecognizer.numberOfTouches>1){ - CGRect newFrame= (self.pinchStartingFrame); - - // upper point? - CGPoint point1 = [gestureRecognizer locationOfTouch:0 inView:self.view]; - CGPoint point2 = [gestureRecognizer locationOfTouch:1 inView:self.view]; - - - newFrame.origin.x = point1.x; - newFrame.origin.y = point1.y; - - newFrame.origin.x = (point1.x > point2.x) ? point2.x : point1.x; - newFrame.origin.y = (point1.y > point2.y) ? point2.y : point1.y; - - newFrame.size.width = (point1.x > point2.x) ? point1.x - point2.x : point2.x - point1.x; - newFrame.size.height = (point1.y > point2.y) ? point1.y - point2.y : point2.y - point1.y; - - - self.currentAnnotation.frame = newFrame; - self.currentAnnotation.imageFrame = [self.view convertRect:self.imageView.frame toView:self.currentAnnotation]; - } else { - [self.currentAnnotation setSelected:NO]; - self.currentAnnotation = nil; - } -} - -- (void)tapped:(UIGestureRecognizer *) __unused tapRecognizer { - - // TODO: remove pre-iOS 8 code. - - // This toggles the nav and status bar. Since iOS7 and pre-iOS7 behave weirdly different, - // this might look rather hacky, but hiding the navbar under iOS6 leads to some ugly - // animation effect which is avoided by simply hiding the navbar setting it's alpha to 0. // moritzh - - if (self.navigationController.navigationBar.alpha == 0 || self.navigationController.navigationBarHidden ){ - - [UIView animateWithDuration:0.35 animations:^{ - [self.navigationController setNavigationBarHidden:NO animated:NO]; - - if ([self respondsToSelector:@selector(prefersStatusBarHidden)]) { - [self setNeedsStatusBarAppearanceUpdate]; - } else { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [[UIApplication sharedApplication] setStatusBarHidden:NO]; -#pragma clang diagnostic pop - } - - } completion:^(BOOL __unused finished) { - [self fitImageViewFrame]; - - }]; - } else { - [UIView animateWithDuration:0.35 animations:^{ - [self.navigationController setNavigationBarHidden:YES animated:NO]; - - if ([self respondsToSelector:@selector(prefersStatusBarHidden)]) { - [self setNeedsStatusBarAppearanceUpdate]; - } else { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [[UIApplication sharedApplication] setStatusBarHidden:YES]; -#pragma clang diagnostic pop - } - - } completion:^(BOOL __unused finished) { - [self fitImageViewFrame]; - - }]; - } - -} - -#pragma mark - Helpers - -- (UIView *)firstAnnotationThatIsNotBlur { - for (BITImageAnnotation *annotation in self.imageView.subviews){ - if (![annotation isKindOfClass:[BITBlurImageAnnotation class]]){ - return annotation; - } - } - - return self.imageView; -} - -- (BOOL)canDrawNewAnnotation { - return [self.editingControls selectedSegmentIndex] != UISegmentedControlNoSegment; -} -@end - -#endif /* HOCKEYSDK_FEATURE_FEEDBACK */ diff --git a/submodules/HockeySDK-iOS/Classes/BITInternal.h b/submodules/HockeySDK-iOS/Classes/BITInternal.h deleted file mode 100644 index 9cbe2efde1..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITInternal.h +++ /dev/null @@ -1,8 +0,0 @@ -#import "BITTelemetryObject.h" - -@interface BITInternal : BITTelemetryObject - -@property (nonatomic, copy) NSString *sdkVersion; -@property (nonatomic, copy) NSString *agentVersion; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITInternal.m b/submodules/HockeySDK-iOS/Classes/BITInternal.m deleted file mode 100644 index ed4d46e4a2..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITInternal.m +++ /dev/null @@ -1,39 +0,0 @@ -#import "BITInternal.h" - -/// Data contract class for type Internal. -@implementation BITInternal - -/// -/// Adds all members of this class to a dictionary -/// @returns dictionary to which the members of this class will be added. -/// -- (NSDictionary *)serializeToDictionary { - NSMutableDictionary *dict = [super serializeToDictionary].mutableCopy; - if (self.sdkVersion != nil) { - [dict setObject:self.sdkVersion forKey:@"ai.internal.sdkVersion"]; - } - if (self.agentVersion != nil) { - [dict setObject:self.agentVersion forKey:@"ai.internal.agentVersion"]; - } - return dict; -} - -#pragma mark - NSCoding - -- (instancetype)initWithCoder:(NSCoder *)coder { - self = [super init]; - if(self) { - _sdkVersion = [coder decodeObjectForKey:@"self.sdkVersion"]; - _agentVersion = [coder decodeObjectForKey:@"self.agentVersion"]; - } - - return self; -} - -- (void)encodeWithCoder:(NSCoder *)coder { - [coder encodeObject:self.sdkVersion forKey:@"self.sdkVersion"]; - [coder encodeObject:self.agentVersion forKey:@"self.agentVersion"]; -} - - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITKeychainUtils.h b/submodules/HockeySDK-iOS/Classes/BITKeychainUtils.h deleted file mode 100644 index b7d7981166..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITKeychainUtils.h +++ /dev/null @@ -1,44 +0,0 @@ -// -// SFHFKeychainUtils.h -// -// Created by Buzz Andersen on 10/20/08. -// Based partly on code by Jonathan Wight, Jon Crosby, and Mike Malone. -// Copyright 2008 Sci-Fi Hi-Fi. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - -#import - - -@interface BITKeychainUtils : NSObject { - -} - -+ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error; -//uses the default kSecAttrAccessibleWhenUnlocked -+ (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error; -+ (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting accessibility:(CFTypeRef) accessibility error: (NSError **) error; -+ (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error; -+ (BOOL) purgeItemsForServiceName:(NSString *) serviceName error: (NSError **) error; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITKeychainUtils.m b/submodules/HockeySDK-iOS/Classes/BITKeychainUtils.m deleted file mode 100644 index 9ec9eddb98..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITKeychainUtils.m +++ /dev/null @@ -1,313 +0,0 @@ -// -// SFHFKeychainUtils.m -// -// Created by Buzz Andersen on 10/20/08. -// Based partly on code by Jonathan Wight, Jon Crosby, and Mike Malone. -// Copyright 2008 Sci-Fi Hi-Fi. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - -#import "BITKeychainUtils.h" -#import - -static NSString *BITKeychainUtilsErrorDomain = @"BITKeychainUtilsErrorDomain"; - -@implementation BITKeychainUtils - -+ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError *__autoreleasing *) error { - if (!username || !serviceName) { - if (error != nil) { - *error = [NSError errorWithDomain: BITKeychainUtilsErrorDomain code: -2000 userInfo: nil]; - } - return nil; - } - - if (error != nil) { - *error = nil; - } - - // Set up a query dictionary with the base query attributes: item type (generic), username, and service - - NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass, kSecAttrAccount, kSecAttrService, nil]; - NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword, username, serviceName, nil]; - - NSMutableDictionary *query = [[NSMutableDictionary alloc] initWithObjects: objects forKeys: keys]; - - // First do a query for attributes, in case we already have a Keychain item with no password data set. - // One likely way such an incorrect item could have come about is due to the previous (incorrect) - // version of this code (which set the password as a generic attribute instead of password data). - - NSMutableDictionary *attributeQuery = [query mutableCopy]; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wcast-qual" - [attributeQuery setObject: (id) kCFBooleanTrue forKey:(__bridge_transfer id) kSecReturnAttributes]; -#pragma clang diagnostic pop - CFTypeRef attrResult = NULL; - OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef) attributeQuery, &attrResult); -// NSDictionary *attributeResult = (__bridge_transfer NSDictionary *)attrResult; - if (attrResult) - CFRelease(attrResult); - - if (status != noErr) { - // No existing item found--simply return nil for the password - if (error != nil && status != errSecItemNotFound) { - //Only return an error if a real exception happened--not simply for "not found." - *error = [NSError errorWithDomain: BITKeychainUtilsErrorDomain code: status userInfo: nil]; - } - - return nil; - } - - // We have an existing item, now query for the password data associated with it. - - NSMutableDictionary *passwordQuery = [query mutableCopy]; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wcast-qual" - [passwordQuery setObject: (id) kCFBooleanTrue forKey: (__bridge_transfer id) kSecReturnData]; -#pragma clang diagnostic pop - CFTypeRef resData = NULL; - status = SecItemCopyMatching((__bridge CFDictionaryRef) passwordQuery, (CFTypeRef *) &resData); - NSData *resultData = (__bridge_transfer NSData *)resData; - - if (status != noErr) { - if (status == errSecItemNotFound) { - // We found attributes for the item previously, but no password now, so return a special error. - // Users of this API will probably want to detect this error and prompt the user to - // re-enter their credentials. When you attempt to store the re-entered credentials - // using storeUsername:andPassword:forServiceName:updateExisting:error - // the old, incorrect entry will be deleted and a new one with a properly encrypted - // password will be added. - if (error != nil) { - *error = [NSError errorWithDomain: BITKeychainUtilsErrorDomain code: -1999 userInfo: nil]; - } - } - else { - // Something else went wrong. Simply return the normal Keychain API error code. - if (error != nil) { - *error = [NSError errorWithDomain: BITKeychainUtilsErrorDomain code: status userInfo: nil]; - } - } - - return nil; - } - - NSString *password = nil; - - if (resultData) { - password = [[NSString alloc] initWithData: resultData encoding: NSUTF8StringEncoding]; - } - else { - // There is an existing item, but we weren't able to get password data for it for some reason, - // Possibly as a result of an item being incorrectly entered by the previous code. - // Set the -1999 error so the code above us can prompt the user again. - if (error != nil) { - *error = [NSError errorWithDomain: BITKeychainUtilsErrorDomain code: -1999 userInfo: nil]; - } - } - - return password; -} - -+ (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError *__autoreleasing *) error { - return [self storeUsername:username andPassword:password forServiceName:serviceName updateExisting:updateExisting accessibility:kSecAttrAccessibleAlways error:error]; -} - -+ (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting accessibility:(CFTypeRef) accessibility error: (NSError *__autoreleasing *) error -{ - if (!username || !password || !serviceName) - { - if (error != nil) - { - *error = [NSError errorWithDomain: BITKeychainUtilsErrorDomain code: -2000 userInfo: nil]; - } - return NO; - } - - // See if we already have a password entered for these credentials. - NSError *getError = nil; - NSString *existingPassword = [BITKeychainUtils getPasswordForUsername: username andServiceName: serviceName error:&getError]; - - if ([getError code] == -1999) - { - // There is an existing entry without a password properly stored (possibly as a result of the previous incorrect version of this code. - // Delete the existing item before moving on entering a correct one. - - getError = nil; - - [self deleteItemForUsername: username andServiceName: serviceName error: &getError]; - - if ([getError code] != noErr) - { - if (error != nil) - { - *error = getError; - } - return NO; - } - } - else if ([getError code] != noErr) - { - if (error != nil) - { - *error = getError; - } - return NO; - } - - if (error != nil) - { - *error = nil; - } - - OSStatus status = noErr; - - if (existingPassword) - { - // We have an existing, properly entered item with a password. - // Update the existing item. - - if (![existingPassword isEqualToString:password] && updateExisting) - { - //Only update if we're allowed to update existing. If not, simply do nothing. - - NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass, - kSecAttrService, - kSecAttrLabel, - kSecAttrAccount, - kSecAttrAccessible, - nil]; - - NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword, - serviceName, - serviceName, - username, - accessibility, - nil]; - - NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys]; - - status = SecItemUpdate((__bridge CFDictionaryRef) query, (__bridge CFDictionaryRef) [NSDictionary dictionaryWithObject: (NSData *)[password dataUsingEncoding: NSUTF8StringEncoding] forKey: (__bridge_transfer NSString *) kSecValueData]); - } - } - else - { - // No existing entry (or an existing, improperly entered, and therefore now - // deleted, entry). Create a new entry. - - NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass, - kSecAttrService, - kSecAttrLabel, - kSecAttrAccount, - kSecValueData, - kSecAttrAccessible, - nil]; - - NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword, - serviceName, - serviceName, - username, - [password dataUsingEncoding: NSUTF8StringEncoding], - accessibility, - nil]; - - NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys]; - - status = SecItemAdd((__bridge CFDictionaryRef) query, NULL); - } - - if (error != nil && status != noErr) - { - // Something went wrong with adding the new item. Return the Keychain error code. - *error = [NSError errorWithDomain: BITKeychainUtilsErrorDomain code: status userInfo: nil]; - - return NO; - } - - return YES; -} - -+ (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError *__autoreleasing *) error -{ - if (!username || !serviceName) - { - if (error != nil) - { - *error = [NSError errorWithDomain: BITKeychainUtilsErrorDomain code: -2000 userInfo: nil]; - } - return NO; - } - - if (error != nil) - { - *error = nil; - } - - NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass, kSecAttrAccount, kSecAttrService, kSecReturnAttributes, nil]; - NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword, username, serviceName, kCFBooleanTrue, nil]; - - NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys]; - - OSStatus status = SecItemDelete((__bridge CFDictionaryRef) query); - - if (error != nil && status != noErr) - { - *error = [NSError errorWithDomain: BITKeychainUtilsErrorDomain code: status userInfo: nil]; - - return NO; - } - - return YES; -} - -+ (BOOL) purgeItemsForServiceName:(NSString *) serviceName error: (NSError *__autoreleasing *) error { - if (!serviceName) - { - if (error != nil) - { - *error = [NSError errorWithDomain: BITKeychainUtilsErrorDomain code: -2000 userInfo: nil]; - } - return NO; - } - - if (error != nil) - { - *error = nil; - } - - NSMutableDictionary *searchData = [NSMutableDictionary new]; - [searchData setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass]; - [searchData setObject:serviceName forKey:(__bridge id)kSecAttrService]; - - OSStatus status = SecItemDelete((__bridge CFDictionaryRef)searchData); - - if (error != nil && status != noErr) - { - *error = [NSError errorWithDomain: BITKeychainUtilsErrorDomain code: status userInfo: nil]; - - return NO; - } - - return YES; -} - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITMetricsManager.h b/submodules/HockeySDK-iOS/Classes/BITMetricsManager.h deleted file mode 100644 index 6d5e3f2d5f..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITMetricsManager.h +++ /dev/null @@ -1,60 +0,0 @@ -#import "HockeySDKFeatureConfig.h" - -#if HOCKEYSDK_FEATURE_METRICS - -#import -#import "BITHockeyBaseManager.h" - -#import "HockeySDKNullability.h" -NS_ASSUME_NONNULL_BEGIN - -/** - The metrics module. - - This is the HockeySDK module that handles users, sessions and events tracking. - - Unless disabled, this module automatically tracks users and session of your app to give you - better insights about how your app is being used. - Users are tracked in a completely anonymous way without collecting any personally identifiable - information. - - Before starting to track events, ask yourself the questions that you want to get answers to. - For instance, you might be interested in business, performance/quality or user experience aspects. - Name your events in a meaningful way and keep in mind that you will use these names - when searching for events in the HockeyApp web portal. - - It is your reponsibility to not collect personal information as part of the events tracking or get - prior consent from your users as necessary. - */ -@interface BITMetricsManager : BITHockeyBaseManager - -/** - * A property indicating whether the BITMetricsManager instance is disabled. - */ -@property (nonatomic, assign) BOOL disabled; - -/** - * This method allows to track an event that happened in your app. - * Remember to choose meaningful event names to have the best experience when diagnosing your app - * in the HockeyApp web portal. - * - * @param eventName The event's name as a string. - */ -- (void)trackEventWithName:(nonnull NSString *)eventName; - -/** - * This method allows to track an event that happened in your app. - * Remember to choose meaningful event names to have the best experience when diagnosing your app - * in the web portal. - * - * @param eventName the name of the event, which should be tracked. - * @param properties key value pairs with additional info about the event. - * @param measurements key value pairs, which contain custom metrics. - */ -- (void)trackEventWithName:(nonnull NSString *)eventName properties:(nullable NSDictionary *)properties measurements:(nullable NSDictionary *)measurements; - -@end - -NS_ASSUME_NONNULL_END - -#endif /* HOCKEYSDK_FEATURE_METRICS */ diff --git a/submodules/HockeySDK-iOS/Classes/BITMetricsManager.m b/submodules/HockeySDK-iOS/Classes/BITMetricsManager.m deleted file mode 100644 index d7dbeeecc5..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITMetricsManager.m +++ /dev/null @@ -1,291 +0,0 @@ -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_METRICS - -#import "BITMetricsManager.h" -#import "BITTelemetryContext.h" -#import "BITMetricsManagerPrivate.h" -#import "BITHockeyHelper.h" -#import "BITHockeyHelper+Application.h" -#import "HockeySDKPrivate.h" -#import "BITChannelPrivate.h" -#import "BITEventData.h" -#import "BITSession.h" -#import "BITSessionState.h" -#import "BITSessionStateData.h" -#import "BITPersistencePrivate.h" -#import "BITHockeyBaseManagerPrivate.h" -#import "BITSender.h" - -NSString *const kBITApplicationWasLaunched = @"BITApplicationWasLaunched"; - -static char *const kBITMetricsEventQueue = "net.hockeyapp.telemetryEventQueue"; - -static NSString *const kBITSessionFileType = @"plist"; -static NSString *const kBITApplicationDidEnterBackgroundTime = @"BITApplicationDidEnterBackgroundTime"; - -static NSString *const BITMetricsBaseURLString = @"https://gate.hockeyapp.net/"; -static NSString *const BITMetricsURLPathString = @"v2/track"; - -@interface BITMetricsManager () - -@property (nonatomic, strong) id appWillEnterForegroundObserver; -@property (nonatomic, strong) id appDidEnterBackgroundObserver; - -@end - -@implementation BITMetricsManager - -@synthesize channel = _channel; -@synthesize telemetryContext = _telemetryContext; -@synthesize persistence = _persistence; -@synthesize serverURL = _serverURL; -@synthesize userDefaults = _userDefaults; - -#pragma mark - Create & start instance - -- (instancetype)init { - if ((self = [super init])) { - _disabled = NO; - _metricsEventQueue = dispatch_queue_create(kBITMetricsEventQueue, DISPATCH_QUEUE_CONCURRENT); - _appBackgroundTimeBeforeSessionExpires = 20; - _serverURL = [NSString stringWithFormat:@"%@%@", BITMetricsBaseURLString, BITMetricsURLPathString]; - } - return self; -} - -- (instancetype)initWithChannel:(BITChannel *)channel telemetryContext:(BITTelemetryContext *)telemetryContext persistence:(BITPersistence *)persistence userDefaults:(NSUserDefaults *)userDefaults { - if ((self = [self init])) { - _channel = channel; - _telemetryContext = telemetryContext; - _persistence = persistence; - _userDefaults = userDefaults; - } - return self; -} - -- (void)startManager { - self.sender = [[BITSender alloc] initWithPersistence:self.persistence serverURL:(NSURL *)[NSURL URLWithString:self.serverURL]]; - [self.sender sendSavedDataAsync]; - [self startNewSessionWithId:bit_UUID()]; - [self registerObservers]; -} - -#pragma mark - Configuration - -- (void)setDisabled:(BOOL)disabled { - if (_disabled == disabled) { return; } - _disabled = disabled; - if (disabled) { - [self unregisterObservers]; - } else { - [self startManager]; - } -} - -#pragma mark - Sessions - -- (void)registerObservers { - NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; - __weak typeof(self) weakSelf = self; - - if (nil == self.appDidEnterBackgroundObserver) { - self.appDidEnterBackgroundObserver = - [center addObserverForName:UIApplicationDidEnterBackgroundNotification - object:nil - queue:NSOperationQueue.mainQueue - usingBlock:^(NSNotification __unused *note) { - typeof(self) strongSelf = weakSelf; - [strongSelf updateDidEnterBackgroundTime]; - }]; - } - if (nil == self.appWillEnterForegroundObserver) { - self.appWillEnterForegroundObserver = - [center addObserverForName:UIApplicationWillEnterForegroundNotification - object:nil - queue:NSOperationQueue.mainQueue - usingBlock:^(NSNotification __unused *note) { - typeof(self) strongSelf = weakSelf; - [strongSelf startNewSessionIfNeeded]; - }]; - } -} - -- (void)unregisterObservers { - NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; - id appDidEnterBackgroundObserver = self.appDidEnterBackgroundObserver; - if(appDidEnterBackgroundObserver) { - [center removeObserver:appDidEnterBackgroundObserver]; - self.appDidEnterBackgroundObserver = nil; - } - id appWillEnterForegroundObserver = self.appWillEnterForegroundObserver; - if(appWillEnterForegroundObserver) { - [center removeObserver:appWillEnterForegroundObserver]; - self.appWillEnterForegroundObserver = nil; - } -} - -- (void)updateDidEnterBackgroundTime { - [self.userDefaults setDouble:[[NSDate date] timeIntervalSince1970] forKey:kBITApplicationDidEnterBackgroundTime]; -} - -- (void)startNewSessionIfNeeded { - double appDidEnterBackgroundTime = [self.userDefaults doubleForKey:kBITApplicationDidEnterBackgroundTime]; - // Add safeguard in case this returns a negative value - if(appDidEnterBackgroundTime < 0) { - appDidEnterBackgroundTime = 0; - [self.userDefaults setDouble:0 forKey:kBITApplicationDidEnterBackgroundTime]; - } - double timeSinceLastBackground = [[NSDate date] timeIntervalSince1970] - appDidEnterBackgroundTime; - if (timeSinceLastBackground > self.appBackgroundTimeBeforeSessionExpires) { - [self startNewSessionWithId:bit_UUID()]; - } -} - -- (void)startNewSessionWithId:(NSString *)sessionId { - BITSession *newSession = [self createNewSessionWithId:sessionId]; - [self.telemetryContext setSessionId:newSession.sessionId]; - [self.telemetryContext setIsFirstSession:newSession.isFirst]; - [self.telemetryContext setIsNewSession:newSession.isNew]; - [self trackSessionWithState:BITSessionState_start]; -} - -- (BITSession *)createNewSessionWithId:(NSString *)sessionId { - BITSession *session = [BITSession new]; - session.sessionId = sessionId; - session.isNew = @"true"; - - if (![self.userDefaults boolForKey:kBITApplicationWasLaunched]) { - session.isFirst = @"true"; - [self.userDefaults setBool:YES forKey:kBITApplicationWasLaunched]; - } else { - session.isFirst = @"false"; - } - return session; -} - -#pragma mark - Track telemetry - -#pragma mark Sessions - -- (void)trackSessionWithState:(BITSessionState)state { - if (self.disabled) { - BITHockeyLogDebug(@"INFO: BITMetricsManager is disabled, therefore this tracking call was ignored."); - return; - } - BITSessionStateData *sessionStateData = [BITSessionStateData new]; - sessionStateData.state = state; - [self.channel enqueueTelemetryItem:sessionStateData]; -} - -#pragma mark Events - -- (void)trackEventWithName:(nonnull NSString *)eventName { - if (!eventName) { - return; - } - if (self.disabled) { - BITHockeyLogDebug(@"INFO: BITMetricsManager is disabled, therefore this tracking call was ignored."); - return; - } - - __weak typeof(self) weakSelf = self; - dispatch_group_t group = dispatch_group_create(); - dispatch_group_async(group, self.metricsEventQueue, ^{ - typeof(self) strongSelf = weakSelf; - BITEventData *eventData = [BITEventData new]; - [eventData setName:eventName]; - [strongSelf trackDataItem:eventData]; - }); - - // If the app is running in the background. - UIApplication *application = [UIApplication sharedApplication]; - BOOL applicationIsInBackground = ([BITHockeyHelper applicationState] == BITApplicationStateBackground); - if (applicationIsInBackground) { - [self.channel createBackgroundTaskWhileDataIsSending:application withWaitingGroup:group]; - } -} - -- (void)trackEventWithName:(nonnull NSString *)eventName - properties:(nullable NSDictionary *)properties - measurements:(nullable NSDictionary *)measurements { - if (!eventName) { - return; - } - if (self.disabled) { - BITHockeyLogDebug(@"INFO: BITMetricsManager is disabled, therefore this tracking call was ignored."); - return; - } - - __weak typeof(self) weakSelf = self; - dispatch_group_t group = dispatch_group_create(); - dispatch_group_async(group, self.metricsEventQueue, ^{ - typeof(self) strongSelf = weakSelf; - BITEventData *eventData = [BITEventData new]; - [eventData setName:eventName]; - [eventData setProperties:(NSDictionary *)properties]; - [eventData setMeasurements:measurements]; - [strongSelf trackDataItem:eventData]; - }); - - // If the app is running in the background. - UIApplication *application = [UIApplication sharedApplication]; - BOOL applicationIsInBackground = ([BITHockeyHelper applicationState] == BITApplicationStateBackground); - if (applicationIsInBackground) { - [self.channel createBackgroundTaskWhileDataIsSending:application withWaitingGroup:group]; - } -} - -#pragma mark Track DataItem - -- (void)trackDataItem:(BITTelemetryData *)dataItem { - if (self.disabled) { - BITHockeyLogDebug(@"INFO: BITMetricsManager is disabled, therefore this tracking call was ignored."); - return; - } - - BITHockeyLogDebug(@"INFO: Enqueue telemetry item: %@", dataItem.name); - [self.channel enqueueTelemetryItem:dataItem]; -} - -#pragma mark - Custom getter - -- (BITChannel *)channel { - @synchronized(self) { - if (!_channel) { - _channel = [[BITChannel alloc] initWithTelemetryContext:self.telemetryContext persistence:self.persistence]; - } - return _channel; - } -} - -- (BITTelemetryContext *)telemetryContext { - @synchronized(self) { - if (!_telemetryContext) { - _telemetryContext = [[BITTelemetryContext alloc] initWithAppIdentifier:self.appIdentifier persistence:self.persistence]; - } - return _telemetryContext; - } -} - -- (BITPersistence *)persistence { - @synchronized(self) { - if (!_persistence) { - _persistence = [BITPersistence new]; - } - return _persistence; - } -} - -- (NSUserDefaults *)userDefaults { - @synchronized(self) { - if (!_userDefaults) { - _userDefaults = [NSUserDefaults standardUserDefaults]; - } - return _userDefaults; - } -} - -@end - -#endif /* HOCKEYSDK_FEATURE_METRICS */ diff --git a/submodules/HockeySDK-iOS/Classes/BITMetricsManagerPrivate.h b/submodules/HockeySDK-iOS/Classes/BITMetricsManagerPrivate.h deleted file mode 100644 index cee0f9ae76..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITMetricsManagerPrivate.h +++ /dev/null @@ -1,125 +0,0 @@ -#import "HockeySDKFeatureConfig.h" - -#if HOCKEYSDK_FEATURE_METRICS - -#import "BITMetricsManager.h" -#import "BITSessionState.h" - -@class BITChannel; -@class BITTelemetryContext; -@class BITSession; -@class BITPersistence; -@class BITSender; - -#import "HockeySDKNullability.h" -NS_ASSUME_NONNULL_BEGIN - -FOUNDATION_EXPORT NSString *const kBITApplicationWasLaunched; - -@interface BITMetricsManager() - -/** - * Create a new BITMetricsManager instance by passing the channel, the telemetry context, and persistence instance to use - for processing metrics. This method can be used for dependency injection. - */ -- (instancetype)initWithChannel:(BITChannel *)channel - telemetryContext:(BITTelemetryContext *)telemetryContext - persistence:(BITPersistence *)persistence - userDefaults:(NSUserDefaults *)userDefaults; - -/** - * The user defaults object used to store meta data. - */ -@property (nonatomic, strong, readonly) NSUserDefaults *userDefaults; - -/** - * A channel for collecting new events before storing and sending them. - */ -@property (nonatomic, strong, readonly) BITPersistence *persistence; - -/** - * A channel for collecting new events before storing and sending them. - */ -@property (nonatomic, strong, readonly) BITChannel *channel; - -/** - * A telemetry context which is used to add meta info to events, before they're sent out. - */ -@property (nonatomic, strong, readonly) BITTelemetryContext *telemetryContext; - -/** - * A concurrent queue which creates and processes telemetry items. - */ -@property (nonatomic, strong, readonly) dispatch_queue_t metricsEventQueue; - -/** - * Sender instance to send out telemetry data. - */ -@property (nonatomic, strong) BITSender *sender; - -///----------------------------------------------------------------------------- -/// @name Session Management -///----------------------------------------------------------------------------- - -/** - * The Interval an app has to be in the background until the current session gets renewed. - */ -@property (nonatomic, assign) NSUInteger appBackgroundTimeBeforeSessionExpires; - -/** - * Registers manager for several notifications, which influence the session state. - */ -- (void)registerObservers; - -/** - * Unregisters manager for several notifications, which influence the session state. - */ -- (void)unregisterObservers; - -/** - * Stores the current date before app is sent to background. - * - * @see appBackgroundTimeBeforeSessionExpires - * @see startNewSessionIfNeeded - */ -- (void)updateDidEnterBackgroundTime; - -/** - * Determines whether the current session needs to be renewed or not. - * - * @see appBackgroundTimeBeforeSessionExpires - * @see updateDidEnterBackgroundTime - */ -- (void)startNewSessionIfNeeded; - -/** - * Creates a new session, updates the session context and sends it to the channel. - * - * @param sessionId the id for the new session - */ -- (void)startNewSessionWithId:(NSString *)sessionId; - -/** - * Creates a new session and stores it to NSUserDefaults. - * - * @param sessionId the id for the new session - * @return the newly created session - */ -- (BITSession *)createNewSessionWithId:(NSString *)sessionId; - -///----------------------------------------------------------------------------- -/// @name Track telemetry data -///----------------------------------------------------------------------------- - -/** - * Creates and enqueues a session event for the given state. - * - * @param state value that determines whether the session started or ended - */ -- (void)trackSessionWithState:(BITSessionState)state; - -@end - -NS_ASSUME_NONNULL_END - -#endif /* HOCKEYSDK_FEATURE_METRICS */ diff --git a/submodules/HockeySDK-iOS/Classes/BITPersistence.h b/submodules/HockeySDK-iOS/Classes/BITPersistence.h deleted file mode 100644 index dd2ac4f158..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITPersistence.h +++ /dev/null @@ -1,9 +0,0 @@ -#import - -/** - * A simple class that handles serialisation and deserialisation of bundles of data. - */ -@interface BITPersistence : NSObject - -@end - diff --git a/submodules/HockeySDK-iOS/Classes/BITPersistence.m b/submodules/HockeySDK-iOS/Classes/BITPersistence.m deleted file mode 100644 index cd3646af81..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITPersistence.m +++ /dev/null @@ -1,305 +0,0 @@ -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_METRICS - -#import "BITPersistence.h" -#import "BITPersistencePrivate.h" -#import "HockeySDKPrivate.h" -#import "BITHockeyHelper.h" - -NSString *const BITPersistenceSuccessNotification = @"BITHockeyPersistenceSuccessNotification"; - -static NSString *const kBITTelemetry = @"Telemetry"; -static NSString *const kBITMetaData = @"MetaData"; -static NSString *const kBITFileBaseString = @"hockey-app-bundle-"; -static NSString *const kBITFileBaseStringMeta = @"metadata"; - -static NSString *const kBITHockeyDirectory = @"com.microsoft.HockeyApp"; -static NSString *const kBITTelemetryDirectory = @"Telemetry"; -static NSString *const kBITMetaDataDirectory = @"MetaData"; - -static char const *kBITPersistenceQueueString = "com.microsoft.HockeyApp.persistenceQueue"; -static NSUInteger const BITDefaultFileCount = 50; - -@interface BITPersistence () - -@property (nonatomic) BOOL directorySetupComplete; - -@end - -@implementation BITPersistence - -#pragma mark - Public - -- (instancetype)init { - self = [super init]; - if (self) { - _persistenceQueue = dispatch_queue_create(kBITPersistenceQueueString, DISPATCH_QUEUE_SERIAL); //TODO several queues? - _requestedBundlePaths = [NSMutableArray new]; - _maxFileCount = BITDefaultFileCount; - - // Evantually, there will be old files on disk, the flag will be updated before the first event gets created - _directorySetupComplete = NO; //will be set to true in createDirectoryStructureIfNeeded - - [self createDirectoryStructureIfNeeded]; - } - return self; -} - -/** - * Saves the Bundle using NSKeyedArchiver and NSData's writeToFile:atomically - * Sends out a BITHockeyPersistenceSuccessNotification in case of success - */ -- (void)persistBundle:(NSData *)bundle { - //TODO send out a fail notification? - NSString *fileURL = [self fileURLForType:BITPersistenceTypeTelemetry]; - - if (bundle) { - __weak typeof(self) weakSelf = self; - dispatch_async(self.persistenceQueue, ^{ - typeof(self) strongSelf = weakSelf; - BOOL success = [bundle writeToFile:fileURL atomically:YES]; - if (success) { - BITHockeyLogDebug(@"INFO: Wrote bundle to %@", fileURL); - [strongSelf sendBundleSavedNotification]; - } - else { - BITHockeyLogError(@"Error writing bundle to %@", fileURL); - } - }); - } - else { - BITHockeyLogWarning(@"WARNING: Unable to write %@ as provided bundle was null", fileURL); - } -} - -- (void)persistMetaData:(NSDictionary *)metaData { - NSString *fileURL = [self fileURLForType:BITPersistenceTypeMetaData]; - //TODO send out a notification, too?! - dispatch_async(self.persistenceQueue, ^{ - [NSKeyedArchiver archiveRootObject:metaData toFile:fileURL]; - }); -} - -- (BOOL)isFreeSpaceAvailable { - NSArray *files = [self persistedFilesForType:BITPersistenceTypeTelemetry]; - return files.count < self.maxFileCount; -} - -- (NSString *)requestNextFilePath { - __block NSString *path = nil; - __weak typeof(self) weakSelf = self; - dispatch_sync(self.persistenceQueue, ^() { - typeof(self) strongSelf = weakSelf; - - path = [strongSelf nextURLOfType:BITPersistenceTypeTelemetry]; - - if (path) { - [self.requestedBundlePaths addObject:path]; - } - }); - return path; -} - -- (NSDictionary *)metaData { - NSString *filePath = [self fileURLForType:BITPersistenceTypeMetaData]; - NSObject *bundle = [self bundleAtFilePath:filePath withFileBaseString:kBITFileBaseStringMeta]; - if ([bundle isKindOfClass:NSDictionary.class]) { - return (NSDictionary *) bundle; - } - BITHockeyLogDebug(@"INFO: The context meta data file could not be loaded."); - return [NSDictionary dictionary]; -} - -- (NSObject *)bundleAtFilePath:(NSString *)filePath withFileBaseString:(NSString *)filebaseString { - id bundle = nil; - if (filePath && [filePath rangeOfString:filebaseString].location != NSNotFound) { - bundle = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - } - return bundle; -} - -- (NSData *)dataAtFilePath:(NSString *)path { - NSData *data = nil; - if (path && [path rangeOfString:kBITFileBaseString].location != NSNotFound) { - data = [NSData dataWithContentsOfFile:path]; - } - return data; -} - -/** - * Deletes a file at the given path. - * - * @param path The path to look for a file and delete it. - */ -- (void)deleteFileAtPath:(NSString *)path { - __weak typeof(self) weakSelf = self; - dispatch_sync(self.persistenceQueue, ^() { - typeof(self) strongSelf = weakSelf; - if ([path rangeOfString:kBITFileBaseString].location != NSNotFound) { - NSError *error = nil; - if (![[NSFileManager defaultManager] removeItemAtPath:path error:&error]) { - BITHockeyLogError(@"Error deleting file at path %@", path); - } - else { - BITHockeyLogDebug(@"INFO: Successfully deleted file at path %@", path); - [strongSelf.requestedBundlePaths removeObject:path]; - } - } else { - BITHockeyLogDebug(@"INFO: Empty path, nothing to delete"); - } - }); - -} - -- (void)giveBackRequestedFilePath:(NSString *)filePath { - __weak typeof(self) weakSelf = self; - dispatch_async(self.persistenceQueue, ^() { - typeof(self) strongSelf = weakSelf; - - [strongSelf.requestedBundlePaths removeObject:filePath]; - }); -} - -#pragma mark - Private - -- (nullable NSString *)fileURLForType:(BITPersistenceType)type { - - NSString *fileName = nil; - NSString *filePath; - - switch (type) { - case BITPersistenceTypeMetaData: { - fileName = kBITFileBaseStringMeta; - filePath = [self.appHockeySDKDirectoryPath stringByAppendingPathComponent:kBITMetaDataDirectory]; - break; - }; - case BITPersistenceTypeTelemetry: { - NSString *uuid = bit_UUID(); - fileName = [NSString stringWithFormat:@"%@%@", kBITFileBaseString, uuid]; - filePath = [self.appHockeySDKDirectoryPath stringByAppendingPathComponent:kBITTelemetryDirectory]; - break; - }; - } - - filePath = [filePath stringByAppendingPathComponent:fileName]; - - return filePath; -} - -/** - * Create directory structure if necessary and exclude it from iCloud backup - */ -- (void)createDirectoryStructureIfNeeded { - // Using the local variable looks unnecessary but it actually silences a static analyzer warning. - NSString *appHockeySDKDirectoryPath = [self appHockeySDKDirectoryPath]; - NSURL *appURL = [NSURL fileURLWithPath:appHockeySDKDirectoryPath]; - NSFileManager *fileManager = [NSFileManager defaultManager]; - if (appURL) { - NSError *error = nil; - - // Create HockeySDK folder if needed - if (![fileManager createDirectoryAtURL:appURL withIntermediateDirectories:YES attributes:nil error:&error]) { - BITHockeyLogError(@"ERROR: %@", error.localizedDescription); - return; - } - - // Create metadata subfolder - NSURL *metaDataURL = [appURL URLByAppendingPathComponent:kBITMetaDataDirectory]; - if (![fileManager createDirectoryAtURL:metaDataURL withIntermediateDirectories:YES attributes:nil error:&error]) { - BITHockeyLogError(@"ERROR: %@", error.localizedDescription); - return; - } - - // Create telemetry subfolder - - //NOTE: createDirectoryAtURL:withIntermediateDirectories:attributes:error - //will return YES if the directory already exists and won't override anything. - //No need to check if the directory already exists. - NSURL *telemetryURL = [appURL URLByAppendingPathComponent:kBITTelemetryDirectory]; - if (![fileManager createDirectoryAtURL:telemetryURL withIntermediateDirectories:YES attributes:nil error:&error]) { - BITHockeyLogError(@"ERROR: %@", error.localizedDescription); - return; - } - - //Exclude HockeySDK folder from backup - if (![appURL setResourceValue:@YES - forKey:NSURLIsExcludedFromBackupKey - error:&error]) { - BITHockeyLogError(@"ERROR: Error excluding %@ from backup %@", appURL.lastPathComponent, error.localizedDescription); - } else { - BITHockeyLogDebug(@"INFO: Exclude %@ from backup", appURL); - } - - self.directorySetupComplete = YES; - } -} - -/** - * @returns the URL to the next file depending on the specified type. If there's no file, return nil. - */ -- (NSString *)nextURLOfType:(BITPersistenceType)type { - NSArray *fileNames = [self persistedFilesForType:type]; - if (fileNames && fileNames.count > 0) { - for (NSURL *filename in fileNames) { - NSString *absolutePath = filename.path; - if (![self.requestedBundlePaths containsObject:absolutePath]) { - return absolutePath; - } - } - } - return nil; -} - -- (NSArray *)persistedFilesForType: (BITPersistenceType)type { - NSString *directoryPath = [self folderPathForType:type]; - NSError *error = nil; - NSArray *fileNames = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:[NSURL fileURLWithPath:directoryPath] - includingPropertiesForKeys:@[NSURLNameKey] - options:NSDirectoryEnumerationSkipsHiddenFiles - error:&error]; - return fileNames; -} - -- (NSString *)folderPathForType:(BITPersistenceType)type { - NSString *subFolder = @""; - switch (type) { - case BITPersistenceTypeTelemetry: { - subFolder = kBITTelemetryDirectory; - break; - } - case BITPersistenceTypeMetaData: { - subFolder = kBITMetaDataDirectory; - break; - } - } - return [self.appHockeySDKDirectoryPath stringByAppendingPathComponent:subFolder]; -} - -/** - * Send a BITHockeyPersistenceSuccessNotification to the main thread to notify observers that we have successfully saved a file - * This is typically used to trigger sending. - */ -- (void)sendBundleSavedNotification { - dispatch_async(dispatch_get_main_queue(), ^{ - BITHockeyLogDebug(@"Sending notification: %@", BITPersistenceSuccessNotification); - [[NSNotificationCenter defaultCenter] postNotificationName:BITPersistenceSuccessNotification - object:nil - userInfo:nil]; - }); -} - -- (NSString *)appHockeySDKDirectoryPath { - if (!_appHockeySDKDirectoryPath) { - NSString *appSupportPath = [[NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) lastObject] stringByStandardizingPath]; - if (appSupportPath) { - _appHockeySDKDirectoryPath = [appSupportPath stringByAppendingPathComponent:kBITHockeyDirectory]; - } - } - return _appHockeySDKDirectoryPath; -} - -@end - -#endif /* HOCKEYSDK_FEATURE_METRICS */ - diff --git a/submodules/HockeySDK-iOS/Classes/BITPersistencePrivate.h b/submodules/HockeySDK-iOS/Classes/BITPersistencePrivate.h deleted file mode 100644 index 9bba4df43e..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITPersistencePrivate.h +++ /dev/null @@ -1,144 +0,0 @@ -#import "HockeySDKFeatureConfig.h" - -#if HOCKEYSDK_FEATURE_METRICS - -#import "BITPersistence.h" - -#import "HockeySDKNullability.h" -NS_ASSUME_NONNULL_BEGIN - -@interface BITPersistence () - -/** - * The BITPersistenceType determines if we have a bundle of meta data or telemetry that we want to safe. - */ -typedef NS_ENUM(NSInteger, BITPersistenceType) { - BITPersistenceTypeTelemetry = 0, - BITPersistenceTypeMetaData = 1 -}; - -/** - * Notification that will be send on the main thread to notifiy observers of a successfully saved bundle. - * This is typically used to trigger sending to the server. - */ -FOUNDATION_EXPORT NSString *const BITPersistenceSuccessNotification; - -///----------------------------------------------------------------------------- -/// @name Save/delete bundle of data -///----------------------------------------------------------------------------- - -/** - * A queue which makes file system operations thread safe. - */ -@property (nonatomic, strong) dispatch_queue_t persistenceQueue; - -/** - * Determines how many telemetry files can be on disk at a time. - */ -@property (nonatomic, assign) NSUInteger maxFileCount; - -@property (nonatomic, copy) NSString *appHockeySDKDirectoryPath; - -/** - * An array with all file paths, that have been requested by the sender. If the sender - * triggers a delete, the appropriate path should also be removed here. We keep to - * track of requested bundles to make sure that bundles don't get sent twice at the same - * time by differend http operations. - */ -@property (nonatomic, strong) NSMutableArray *requestedBundlePaths; - -/** - * Saves the bundle to disk. - * - * @param bundle the bundle, which should be saved to disk - */ -- (void)persistBundle:(NSData *)bundle; - -/** - * Saves the given dictionary to the session Ids file. - * - * @param metaData a dictionary consisting of unix timestamps and session ids - */ -- (void)persistMetaData:(NSDictionary *)metaData; - -/** - * Deletes the file for the given path. - * - * @param path the path of the file, which should be deleted - */ -- (void)deleteFileAtPath:(NSString *)path; - -/** - * Determines whether the persistence layer is able to write more files to disk. - * - * @return YES if the maxFileCount has not been reached, yet (otherwise NO). - */ -- (BOOL)isFreeSpaceAvailable; - -///----------------------------------------------------------------------------- -/// @name Get a bundle of saved data -///----------------------------------------------------------------------------- - -/** - * Returns the path for the next item to send. The requested path is reserved as long - * as leaveUpRequestedPath: gets called. - * - * @see giveBackRequestedPath: - * - * @return the path of the item, which should be sent next - */ -- (nullable NSString *)requestNextFilePath; - -/** - * Release a requested path. This method should be called after sending a file failed. - * - * @param filePath The path that should be available for sending again. - */ -- (void)giveBackRequestedFilePath:(NSString *)filePath; - -/** - * Return the json data for a given path - * - * @param filePath The path of the file - * - * @return a data object which contains telemetry data in json representation - */ -- (nullable NSData *)dataAtFilePath:(NSString *)filePath; - -/** - * Returns the content of the session Ids file. - * - * @return return a dictionary containing all session Ids - */ -- (NSDictionary *)metaData; - -///----------------------------------------------------------------------------- -/// @name Getting a path -///----------------------------------------------------------------------------- - -/** - * Returns a folder path for items of a given type. - * @param type The type - * @return a folder path for items of a given type - */ -- (NSString *)folderPathForType:(BITPersistenceType)type; - -///----------------------------------------------------------------------------- -/// @name Getting a path -///----------------------------------------------------------------------------- - -/** - * Creates the path for a file - * The filename includes the timestamp. - * - * @param type The type that you want the fileURL for -*/ -- (nullable NSString *)fileURLForType:(BITPersistenceType)type; - -- (void)createDirectoryStructureIfNeeded; - -#endif /* HOCKEYSDK_FEATURE_METRICS */ - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/HockeySDK-iOS/Classes/BITRectangleImageAnnotation.h b/submodules/HockeySDK-iOS/Classes/BITRectangleImageAnnotation.h deleted file mode 100644 index 024957998a..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITRectangleImageAnnotation.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Author: Moritz Haarmann - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "BITImageAnnotation.h" - -@interface BITRectangleImageAnnotation : BITImageAnnotation - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITRectangleImageAnnotation.m b/submodules/HockeySDK-iOS/Classes/BITRectangleImageAnnotation.m deleted file mode 100644 index ed20737dc7..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITRectangleImageAnnotation.m +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Author: Moritz Haarmann - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_FEEDBACK - -#import "BITRectangleImageAnnotation.h" - -@interface BITRectangleImageAnnotation() - -@property (nonatomic, strong) CAShapeLayer *shapeLayer; -@property (nonatomic, strong) CAShapeLayer *strokeLayer; - -@end - -@implementation BITRectangleImageAnnotation - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - self.shapeLayer = [CAShapeLayer layer]; - self.shapeLayer.strokeColor = [UIColor redColor].CGColor; - self.shapeLayer.lineWidth = 5; - self.shapeLayer.fillColor = [UIColor clearColor].CGColor; - - self.strokeLayer = [CAShapeLayer layer]; - self.strokeLayer.strokeColor = [UIColor whiteColor].CGColor; - self.strokeLayer.lineWidth = 10; - self.strokeLayer.fillColor = [UIColor clearColor].CGColor; - [self.layer addSublayer:self.strokeLayer]; - - [self.layer addSublayer:self.shapeLayer]; - - } - return self; -} - -- (void)layoutSubviews { - [super layoutSubviews]; - - self.shapeLayer.frame = self.bounds; - self.shapeLayer.path = [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:10].CGPath; - - - self.strokeLayer.frame = self.bounds; - self.strokeLayer.path = [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:10].CGPath; - - CGFloat lineWidth = MAX(self.frame.size.width / 10,10); - - [CATransaction begin]; - [CATransaction setAnimationDuration:0]; - self.strokeLayer.lineWidth = lineWidth/(CGFloat)1.5; - self.shapeLayer.lineWidth = lineWidth /(CGFloat)3.0; - - [CATransaction commit]; -} - -- (BOOL)resizable { - return YES; -} - - -@end - -#endif /* HOCKEYSDK_FEATURE_FEEDBACK */ diff --git a/submodules/HockeySDK-iOS/Classes/BITSender.h b/submodules/HockeySDK-iOS/Classes/BITSender.h deleted file mode 100644 index 8ebe6e81d4..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITSender.h +++ /dev/null @@ -1,129 +0,0 @@ -#import -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_METRICS - -@class BITPersistence; - -#import "HockeySDKNullability.h" -NS_ASSUME_NONNULL_BEGIN - -/** - * Utility class that's responsible for sending a bundle of data to the server - */ -@interface BITSender : NSObject - -/** - * Notification that will be send on the main thread to notifiy observers of finish sending data. - */ -FOUNDATION_EXPORT NSString *const BITSenderFinishSendingDataNotification; - -///----------------------------------------------------------------------------- -/// @name Initialize instance -///----------------------------------------------------------------------------- - -/** - * Initializes a sender instance with a given persistence object. - * - * @param persistence used for loading files before sending them out - * @param serverURL the endpoint URL for telemetry data - * @return an initialized sender instance - */ -- (instancetype)initWithPersistence:(BITPersistence *)persistence serverURL:(NSURL *)serverURL; - -/** - * A queue which is used to handle completion blocks. - */ -@property (nonatomic, strong) dispatch_queue_t senderTasksQueue; - -/** - * The endpoint url of the telemetry server. - */ -@property (nonatomic, copy) NSString *endpointPath; - -/** - * The max number of request that can run at a time. - */ -@property (nonatomic, assign) NSUInteger maxRequestCount; - -/** - * The number of requests that are currently running. - */ -@property (atomic, assign) NSUInteger runningRequestsCount; - -/** - * BaseURL to which relative paths are appended. - */ -@property (nonatomic, strong, readonly) NSURL *serverURL; - -/** - * The persistence instance used for loading files before sending them out. - */ -@property (nonatomic, strong, readonly) BITPersistence *persistence; - -///----------------------------------------------------------------------------- -/// @name Sending data -///----------------------------------------------------------------------------- - -/** - * Creates a request for the given data and forwards that in order to send it out. - * - * @param data the telemetry data which should be sent - * @param filePath a reference of filePath to the file which should be sent (needed to delete it after sending) - */ -- (void)sendData:(NSData *)data withFilePath:(NSString * )filePath; - -/** - * Triggers sending the saved data on a background thread. Does nothing if nothing has been persisted, yet. This method should be called on app start. - */ -- (void)sendSavedDataAsync; - -/** - * Triggers sending the saved data. - */ -- (void)sendSavedData; - -/** - * Creates a HTTP operation/session task and puts it to the queue. - * - * @param request a request for sending a data object to the telemetry server - * @param path path to the file which should be sent - */ -- (void)sendRequest:(NSURLRequest *)request filePath:(NSString *)path; - -/** - * Deletes or unblocks sent file according to the given response code. - * - * @param statusCode the status code of the response - * @param responseData the data of the response - * @param filePath the path of the file which content has been sent to the server - * @param error an error object sent from the server - */ -- (void)handleResponseWithStatusCode:(NSInteger)statusCode responseData:(NSData *)responseData filePath:(NSString *)filePath error:(NSError *)error; - -///----------------------------------------------------------------------------- -/// @name Helper -///----------------------------------------------------------------------------- - -/** - * Returns a request for sending data to the telemetry sender. - * - * @param data the data which should be sent - * - * @return a request which contains the given data - */ -- (NSURLRequest *)requestForData:(NSData *)data; - -/** - * Returns if data should be deleted based on a given status code. - * - * @param statusCode the status code which is part of the response object - * - * @return YES if data should be deleted, NO if the payload should be sent at a later time again. - */ -- (BOOL)shouldDeleteDataWithStatusCode:(NSInteger)statusCode; - -@end -NS_ASSUME_NONNULL_END - -#endif /* HOCKEYSDK_FEATURE_METRICS */ diff --git a/submodules/HockeySDK-iOS/Classes/BITSender.m b/submodules/HockeySDK-iOS/Classes/BITSender.m deleted file mode 100644 index 0b6627cd50..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITSender.m +++ /dev/null @@ -1,209 +0,0 @@ -#import "BITSender.h" - -#if HOCKEYSDK_FEATURE_METRICS - -#import "BITPersistencePrivate.h" -#import "BITChannelPrivate.h" -#import "BITGZIP.h" -#import "HockeySDKPrivate.h" -#import "BITHockeyHelper.h" - -NSString *const BITSenderFinishSendingDataNotification = @"BITSenderFinishSendingDataNotification"; - -static char const *kBITSenderTasksQueueString = "net.hockeyapp.sender.tasksQueue"; -static NSUInteger const BITDefaultRequestLimit = 10; - -@interface BITSender () - -@property (nonatomic, strong) NSURLSession *session; - -@property (nonatomic, weak, nullable) id persistenceSuccessObserver; -@property (nonatomic, weak, nullable) id channelBlockedObserver; - -@end - -@implementation BITSender - -@synthesize runningRequestsCount = _runningRequestsCount; -@synthesize persistence = _persistence; - -#pragma mark - Initialize instance - -- (instancetype)initWithPersistence:(nonnull BITPersistence *)persistence serverURL:(nonnull NSURL *)serverURL { - if ((self = [super init])) { - _senderTasksQueue = dispatch_queue_create(kBITSenderTasksQueueString, DISPATCH_QUEUE_CONCURRENT); - _maxRequestCount = BITDefaultRequestLimit; - _serverURL = serverURL; - _persistence = persistence; - [self registerObservers]; - } - return self; -} - -- (void)dealloc { - [self unregisterObservers]; -} - -#pragma mark - Handle persistence events - -- (void)registerObservers { - NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; - __weak typeof(self) weakSelf = self; - - if (nil == self.persistenceSuccessObserver) { - self.persistenceSuccessObserver = - [center addObserverForName:BITPersistenceSuccessNotification - object:nil - queue:nil - usingBlock:^(NSNotification __unused *notification) { - typeof(self) strongSelf = weakSelf; - [strongSelf sendSavedDataAsync]; - }]; - } - if (nil == self.channelBlockedObserver) { - self.channelBlockedObserver = - [center addObserverForName:BITChannelBlockedNotification - object:nil - queue:nil - usingBlock:^(NSNotification __unused *notification) { - typeof(self) strongSelf = weakSelf; - [strongSelf sendSavedDataAsync]; - }]; - } -} - -- (void)unregisterObservers { - NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; - id persistenceSuccessObserver = self.persistenceSuccessObserver; - if(persistenceSuccessObserver) { - [center removeObserver:persistenceSuccessObserver]; - self.persistenceSuccessObserver = nil; - } - id channelBlockedObserver = self.channelBlockedObserver; - if(channelBlockedObserver) { - [center removeObserver:channelBlockedObserver]; - self.channelBlockedObserver = nil; - } -} - -#pragma mark - Sending - -- (void)sendSavedDataAsync { - dispatch_async(self.senderTasksQueue, ^{ - [self sendSavedData]; - }); -} - -- (void)sendSavedData { - @synchronized(self) { - if (self.runningRequestsCount < self.maxRequestCount) { - self.runningRequestsCount++; - BITHockeyLogDebug(@"INFO: Create new sender thread. Current count is %ld", (long) self.runningRequestsCount); - } else { - return; - } - } - - NSString *filePath = [self.persistence requestNextFilePath]; - NSData *data = [self.persistence dataAtFilePath:filePath]; - [self sendData:data withFilePath:filePath]; -} - -- (void)sendData:(nonnull NSData *)data withFilePath:(nonnull NSString *)filePath { - if (data && data.length > 0) { - NSData *gzippedData = [data bit_gzippedData]; - NSURLRequest *request = [self requestForData:gzippedData]; - - BITHockeyLogVerbose(@"VERBOSE: Sending data:\n%@", [[NSString alloc] initWithData:data encoding:kCFStringEncodingUTF8]); - [self sendRequest:request filePath:filePath]; - } else { - self.runningRequestsCount--; - BITHockeyLogDebug(@"INFO: Close sender thread due empty package. Current count is %ld", (long) self.runningRequestsCount); - } -} - -- (void)sendRequest:(nonnull NSURLRequest *)request filePath:(nonnull NSString *)path { - if (!path || !request) { - return; - } - NSURLSession *session = self.session; - NSURLSessionDataTask *task = [session dataTaskWithRequest:request - completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response; - NSInteger statusCode = httpResponse.statusCode; - [self handleResponseWithStatusCode:statusCode responseData:data filePath:path error:error]; - }]; - [task resume]; -} - -- (void)handleResponseWithStatusCode:(NSInteger)statusCode responseData:(nonnull NSData *)responseData filePath:(nonnull NSString *)filePath error:(nonnull NSError *)error { - self.runningRequestsCount--; - BITHockeyLogDebug(@"INFO: Close sender thread due incoming response. Current count is %ld", (long) self.runningRequestsCount); - - if (responseData && (responseData.length > 0) && [self shouldDeleteDataWithStatusCode:statusCode]) { - //we delete data that was either sent successfully or if we have a non-recoverable error - BITHockeyLogDebug(@"INFO: Sent data with status code: %ld", (long) statusCode); - BITHockeyLogDebug(@"INFO: Response data:\n%@", [NSJSONSerialization JSONObjectWithData:responseData options:0 error:nil]); - [self.persistence deleteFileAtPath:filePath]; - [self sendSavedData]; - } else { - BITHockeyLogError(@"ERROR: Sending telemetry data failed"); - BITHockeyLogError(@"Error description: %@", error.localizedDescription); - [self.persistence giveBackRequestedFilePath:filePath]; - } - - if (self.runningRequestsCount == 0) { - [self sendSenderFinishSendingDataNotification]; - } -} - -- (void)sendSenderFinishSendingDataNotification { - dispatch_async(dispatch_get_main_queue(), ^{ - BITHockeyLogDebug(@"Sending notification: %@", BITSenderFinishSendingDataNotification); - [[NSNotificationCenter defaultCenter] postNotificationName:BITSenderFinishSendingDataNotification - object:nil - userInfo:nil]; - }); -} - -#pragma mark - Helper - -- (NSURLRequest *)requestForData:(nonnull NSData *)data { - - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:self.serverURL]; - request.HTTPMethod = @"POST"; - - request.HTTPBody = data; - request.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData; - - NSDictionary *headers = @{@"Charset" : @"UTF-8", - @"Content-Encoding" : @"gzip", - @"Content-Type" : @"application/x-json-stream", - @"Accept-Encoding" : @"gzip"}; - [request setAllHTTPHeaderFields:headers]; - - return request; -} - -//some status codes represent recoverable error codes -//we try sending again some point later -- (BOOL)shouldDeleteDataWithStatusCode:(NSInteger)statusCode { - NSArray *recoverableStatusCodes = @[@429, @408, @500, @503, @511]; - - return ![recoverableStatusCodes containsObject:@(statusCode)]; -} - -#pragma mark - Getter/Setter - -- (NSURLSession *)session { - if (!_session) { - NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; - _session = [NSURLSession sessionWithConfiguration:sessionConfiguration]; - } - return _session; -} - -@end - -#endif /* HOCKEYSDK_FEATURE_METRICS */ - diff --git a/submodules/HockeySDK-iOS/Classes/BITSession.h b/submodules/HockeySDK-iOS/Classes/BITSession.h deleted file mode 100644 index 04843ee2d3..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITSession.h +++ /dev/null @@ -1,9 +0,0 @@ -#import "BITTelemetryObject.h" - -@interface BITSession : BITTelemetryObject - -@property (nonatomic, copy) NSString *sessionId; -@property (nonatomic, copy) NSString *isFirst; -@property (nonatomic, copy) NSString *isNew; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITSession.m b/submodules/HockeySDK-iOS/Classes/BITSession.m deleted file mode 100644 index e8871bdd38..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITSession.m +++ /dev/null @@ -1,45 +0,0 @@ -#import "BITSession.h" - -/// Data contract class for type Session. -@implementation BITSession - -/// -/// Adds all members of this class to a dictionary -/// @returns dictionary to which the members of this class will be added. -/// -- (NSDictionary *)serializeToDictionary { - NSMutableDictionary *dict = [super serializeToDictionary].mutableCopy; - if (self.sessionId != nil) { - [dict setObject:self.sessionId forKey:@"ai.session.id"]; - } - if (self.isFirst != nil) { - [dict setObject:self.isFirst forKey:@"ai.session.isFirst"]; - } - if (self.isNew != nil) { - [dict setObject:self.isNew forKey:@"ai.session.isNew"]; - } - return dict; -} - -#pragma mark - NSCoding - -- (instancetype)initWithCoder:(NSCoder *)coder { - self = [super initWithCoder:coder]; - if(self) { - _sessionId = [coder decodeObjectForKey:@"self.sessionId"]; - _isFirst = [coder decodeObjectForKey:@"self.isFirst"]; - _isNew = [coder decodeObjectForKey:@"self.isNew"]; - } - - return self; -} - -- (void)encodeWithCoder:(NSCoder *)coder { - [super encodeWithCoder:coder]; - [coder encodeObject:self.sessionId forKey:@"self.sessionId"]; - [coder encodeObject:self.isFirst forKey:@"self.isFirst"]; - [coder encodeObject:self.isNew forKey:@"self.isNew"]; -} - - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITSessionState.h b/submodules/HockeySDK-iOS/Classes/BITSessionState.h deleted file mode 100755 index 10c9368b53..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITSessionState.h +++ /dev/null @@ -1,8 +0,0 @@ -#import -/// Enum class for type SessionState. -typedef NS_ENUM(NSInteger, BITSessionState) { - BITSessionState_start = 0, - - BITSessionState_end = 1, - -}; diff --git a/submodules/HockeySDK-iOS/Classes/BITSessionStateData.h b/submodules/HockeySDK-iOS/Classes/BITSessionStateData.h deleted file mode 100755 index 63192c92f8..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITSessionStateData.h +++ /dev/null @@ -1,10 +0,0 @@ -#import "BITDomain.h" -#import "BITSessionState.h" - -@interface BITSessionStateData : BITDomain - -@property (nonatomic, copy, readonly) NSString *envelopeTypeName; -@property (nonatomic, copy, readonly) NSString *dataTypeName; -@property (nonatomic, assign) BITSessionState state; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITSessionStateData.m b/submodules/HockeySDK-iOS/Classes/BITSessionStateData.m deleted file mode 100755 index ee755f25b7..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITSessionStateData.m +++ /dev/null @@ -1,49 +0,0 @@ -#import "BITSessionStateData.h" - -/// Data contract class for type SessionStateData. -@implementation BITSessionStateData -@synthesize envelopeTypeName = _envelopeTypeName; -@synthesize dataTypeName = _dataTypeName; -@synthesize version = _version; - -/// Initializes a new instance of the class. -- (instancetype)init { - if((self = [super init])) { - _envelopeTypeName = @"Microsoft.ApplicationInsights.SessionState"; - _dataTypeName = @"SessionStateData"; - _version = @2; - _state = BITSessionState_start; - } - return self; -} - -/// -/// Adds all members of this class to a dictionary -/// @returns dictionary to which the members of this class will be added. -/// -- (NSDictionary *)serializeToDictionary { - NSMutableDictionary *dict = [super serializeToDictionary].mutableCopy; - [dict setObject:[NSNumber numberWithInt:(int)self.state] forKey:@"state"]; - return dict; -} - -#pragma mark - NSCoding - -- (instancetype)initWithCoder:(NSCoder *)coder { - self = [super initWithCoder:coder]; - if(self) { - _envelopeTypeName =[coder decodeObjectForKey:@"envelopeTypeName"]; - _dataTypeName = [coder decodeObjectForKey:@"dataTypeName"]; - _state = (BITSessionState)[coder decodeIntForKey:@"self.state"]; - } - return self; -} - -- (void)encodeWithCoder:(NSCoder *)coder { - [super encodeWithCoder:coder]; - [coder encodeObject:self.envelopeTypeName forKey:@"envelopeTypeName"]; - [coder encodeObject:self.dataTypeName forKey:@"dataTypeName"]; - [coder encodeInt:self.state forKey:@"self.state"]; -} - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITStoreButton.h b/submodules/HockeySDK-iOS/Classes/BITStoreButton.h deleted file mode 100644 index d44f3bb216..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITStoreButton.h +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Author: Andreas Linde - * Peter Steinberger - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * Copyright (c) 2011-2012 Peter Steinberger. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - - -#import - -// defines a button action set (data container) -@interface BITStoreButtonData : NSObject - -+ (id)dataWithLabel:(NSString*)aLabel enabled:(BOOL)flag; - -@property (nonatomic, copy) NSString *label; -@property (nonatomic, assign, getter=isEnabled) BOOL enabled; - -@end - - -@class BITStoreButton; -@protocol BITStoreButtonDelegate -- (void)storeButtonFired:(BITStoreButton *)button; -@end - -/** - * Button style depending on the iOS version - */ -typedef NS_ENUM(NSUInteger, BITStoreButtonStyle) { - /** - * Draw buttons in the iOS 7 style - */ - BITStoreButtonStyleOS7 = 0 -}; - - -// Simulate the Payment Button from the AppStore -// The interface is flexible, so there is now fixed order -@interface BITStoreButton : UIButton - -- (instancetype)initWithFrame:(CGRect)frame; -- (instancetype)initWithPadding:(CGPoint)padding style:(BITStoreButtonStyle)style; - -// action delegate -@property (nonatomic, weak) id buttonDelegate; - -// change the button layer -@property (nonatomic, strong) BITStoreButtonData *buttonData; -- (void)setButtonData:(BITStoreButtonData *)aButtonData animated:(BOOL)animated; - -// align helper -@property (nonatomic, assign) CGPoint customPadding; - -// align helper -@property (nonatomic, assign) BITStoreButtonStyle style; - - -- (void)alignToSuperview; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITStoreButton.m b/submodules/HockeySDK-iOS/Classes/BITStoreButton.m deleted file mode 100644 index 8ac67a3e22..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITStoreButton.m +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Author: Andreas Linde - * Peter Steinberger - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * Copyright (c) 2011-2012 Peter Steinberger. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_UPDATES - -#import "BITStoreButton.h" -#import "HockeySDKPrivate.h" -#import - -#define BIT_MIN_HEIGHT 25.0 -#define BIT_MAX_WIDTH 120.0 -#define BIT_PADDING 12.0 -#define kDefaultButtonAnimationTime 0.25 - - -@implementation BITStoreButtonData - -#pragma mark - NSObject - -- (instancetype)initWithLabel:(NSString*)aLabel enabled:(BOOL)flag { - if ((self = [super init])) { - self.label = aLabel; - self.enabled = flag; - } - return self; -} - -+ (id)dataWithLabel:(NSString*)aLabel enabled:(BOOL)flag { - return [[[self class] alloc] initWithLabel:aLabel enabled:flag]; -} - -@end - -@interface BITStoreButton () - -@property (nonatomic, strong) CALayer *defaultBorderLayer; -@property (nonatomic, strong) CALayer *inActiveBorderLayer; - -@end - -@implementation BITStoreButton - -#pragma mark - private - -- (void)buttonPressed:(id) __unused sender { - [self.buttonDelegate storeButtonFired:self]; -} - -- (void)animationDidStop:(NSString *) __unused animationID finished:(NSNumber *)finished context:(void *) __unused context { - // show text again, but only if animation did finish (or else another animation is on the way) - if ([finished boolValue]) { - [self setTitle:self.buttonData.label forState:UIControlStateNormal]; - } -} - -- (void)updateButtonAnimated:(BOOL)animated { - if (animated) { - // hide text, then start animation - [self setTitle:@"" forState:UIControlStateNormal]; - [UIView beginAnimations:@"storeButtonUpdate" context:nil]; - [UIView setAnimationBeginsFromCurrentState:YES]; - [UIView setAnimationDuration:kDefaultButtonAnimationTime]; - [UIView setAnimationDelegate:self]; - [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)]; - } else { - [self setTitle:self.buttonData.label forState:UIControlStateNormal]; - } - - self.enabled = self.buttonData.isEnabled; - - // show white or gray text, depending on the state - if (self.buttonData.isEnabled) { - [self setTitleColor:BIT_RGBCOLOR(35, 111, 251) forState:UIControlStateNormal]; - [self.defaultBorderLayer setHidden:NO]; - [self.inActiveBorderLayer setHidden:YES]; - } else { - [self setTitleColor:BIT_RGBCOLOR(148, 150, 151) forState:UIControlStateNormal]; - if (self.style == BITStoreButtonStyleOS7) { - [self.defaultBorderLayer setHidden:YES]; - [self.inActiveBorderLayer setHidden:NO]; - } - } - - // calculate optimal new size - CGSize sizeThatFits = [self sizeThatFits:CGSizeZero]; - - // move sublayer (can't be animated explcitely) - for (CALayer *aLayer in self.layer.sublayers) { - [CATransaction begin]; - - if (animated) { - [CATransaction setAnimationDuration:kDefaultButtonAnimationTime]; - [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]]; - } else { - // frame is calculated and explicitly animated. so we absolutely need kCATransactionDisableActions - [CATransaction setValue:[NSNumber numberWithBool:YES] forKey:kCATransactionDisableActions]; - } - - CGRect newFrame = aLayer.frame; - newFrame.size.width = sizeThatFits.width; - aLayer.frame = newFrame; - - [CATransaction commit]; - } - - // set outer frame changes - self.titleEdgeInsets = UIEdgeInsetsMake(2.0, self.titleEdgeInsets.left, 0.0, 0.0); - [self alignToSuperview]; - - if (animated) { - [UIView commitAnimations]; - } -} - -- (void)alignToSuperview { - [self sizeToFit]; - if (self.superview) { - CGRect cr = self.frame; - cr.origin.y = self.customPadding.y; - cr.origin.x = self.superview.frame.size.width - cr.size.width - self.customPadding.x * 2; - self.frame = cr; - } -} - - -#pragma mark - NSObject - -- (instancetype)initWithFrame:(CGRect)frame { - if ((self = [super initWithFrame:frame])) { - self.layer.needsDisplayOnBoundsChange = YES; - - // setup title label - [self.titleLabel setFont:[UIFont boldSystemFontOfSize:13.0]]; - - // register for touch events - [self addTarget:self action:@selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside]; - - [self bringSubviewToFront:(UILabel *)self.titleLabel]; - } - return self; -} - -- (instancetype)initWithPadding:(CGPoint)padding style:(BITStoreButtonStyle)style { - CGRect frame = CGRectMake(0, 0, 40, BIT_MIN_HEIGHT); - if ((self = [self initWithFrame:frame])) { - _customPadding = padding; - _style = style; - - // border layers for more sex! - _defaultBorderLayer = [CALayer layer]; - _defaultBorderLayer.borderColor = [BIT_RGBCOLOR(35, 111, 251) CGColor]; - _defaultBorderLayer.borderWidth = 1.0; - _defaultBorderLayer.frame = CGRectMake(0.0, 0.0, CGRectGetWidth(frame), CGRectGetHeight(frame)); - _defaultBorderLayer.cornerRadius = 2.5; - _defaultBorderLayer.needsDisplayOnBoundsChange = YES; - [self.layer addSublayer:_defaultBorderLayer]; - - if (style == BITStoreButtonStyleOS7) { - _inActiveBorderLayer = [CALayer layer]; - _inActiveBorderLayer.borderColor = [BIT_RGBCOLOR(148, 150, 151) CGColor]; - _inActiveBorderLayer.borderWidth = 1.0; - _inActiveBorderLayer.frame = CGRectMake(0.0, 0.0, CGRectGetWidth(frame), CGRectGetHeight(frame)); - _inActiveBorderLayer.cornerRadius = 2.5; - _inActiveBorderLayer.needsDisplayOnBoundsChange = YES; - [self.layer addSublayer:_inActiveBorderLayer]; - [_inActiveBorderLayer setHidden:YES]; - } - - [self bringSubviewToFront:(UILabel *)self.titleLabel]; - } - return self; -} - - - -#pragma mark - UIView - -- (CGSize)sizeThatFits:(CGSize) __unused size { - CGSize constr = (CGSize){.height = self.frame.size.height, .width = BIT_MAX_WIDTH}; - CGSize newSize; - - if ([self.buttonData.label respondsToSelector:@selector(boundingRectWithSize:options:attributes:context:)]) { - CGRect calculatedRect = [self.buttonData.label boundingRectWithSize:constr - options:NSStringDrawingUsesFontLeading - attributes:@{NSFontAttributeName:(id)self.titleLabel.font} - context:nil]; - newSize = calculatedRect.size; - } else { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - newSize = [self.buttonData.label sizeWithFont:self.titleLabel.font - constrainedToSize:constr - lineBreakMode:NSLineBreakByTruncatingMiddle]; -#pragma clang diagnostic pop - } - - CGFloat newWidth = newSize.width + ((CGFloat)BIT_PADDING * 2); - CGFloat newHeight = (CGFloat)BIT_MIN_HEIGHT > newSize.height ? (CGFloat)BIT_MIN_HEIGHT : newSize.height; - - CGSize sizeThatFits = CGSizeMake(newWidth, newHeight); - return sizeThatFits; -} - -- (void)setFrame:(CGRect)aRect { - [super setFrame:aRect]; - - // copy frame changes to sublayers (but watch out for NaN's) - for (CALayer *aLayer in self.layer.sublayers) { - CGRect rect = aLayer.frame; - rect.size.width = self.frame.size.width; - rect.size.height = self.frame.size.height; - aLayer.frame = rect; - [aLayer layoutIfNeeded]; - } -} - - -#pragma mark - Properties - -- (void)setButtonData:(BITStoreButtonData *)aButtonData { - [self setButtonData:aButtonData animated:NO]; -} - -- (void)setButtonData:(BITStoreButtonData *)aButtonData animated:(BOOL)animated { - if (self.buttonData != aButtonData) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdirect-ivar-access" - _buttonData = aButtonData; -#pragma clang diagnostic pop - } - - [self updateButtonAnimated:animated]; -} - -@end - -#endif /* HOCKEYSDK_FEATURE_UPDATES */ diff --git a/submodules/HockeySDK-iOS/Classes/BITStoreUpdateManager.h b/submodules/HockeySDK-iOS/Classes/BITStoreUpdateManager.h deleted file mode 100644 index b7b01d0233..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITStoreUpdateManager.h +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2013-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - - -#import -#import "BITHockeyBaseManager.h" - - -/** - * Defines the update check intervals - */ -typedef NS_ENUM(NSInteger, BITStoreUpdateSetting) { - /** - * Check every day - */ - BITStoreUpdateCheckDaily = 0, - /** - * Check every week - */ - BITStoreUpdateCheckWeekly = 1, - /** - * Check manually - */ - BITStoreUpdateCheckManually = 2 -}; - -@protocol BITStoreUpdateManagerDelegate; - -/** - The store update manager module. - - This is the HockeySDK module for handling app updates when having your app released in the App Store. - By default the module uses the current users locale to define the app store to check for updates. You - can modify this using the `countryCode` property. See the property documentation for details on its usage. - - When an update is detected, this module will show an alert asking the user if he/she wants to update or - ignore this version. If update was chosen, it will open the apps page in the app store app. - - You need to enable this module using `[BITHockeyManager enableStoreUpdateManager]` if you want to use this - feature. By default this module is disabled! - - When this module is enabled and **NOT** running in an App Store build/environment, it won't do any checks! - - The `BITStoreUpdateManagerDelegate` protocol informs the app about new detected app versions. - - @warning This module can **NOT** check if the current device and OS version match the minimum requirements of - the new app version! - - */ - -@interface BITStoreUpdateManager : BITHockeyBaseManager - -///----------------------------------------------------------------------------- -/// @name Update Checking -///----------------------------------------------------------------------------- - -/** - When to check for new updates. - - Defines when a the SDK should check if there is a new update available on the - server. This must be assigned one of the following, see `BITStoreUpdateSetting`: - - - `BITStoreUpdateCheckDaily`: Once a day - - `BITStoreUpdateCheckWeekly`: Once a week - - `BITStoreUpdateCheckManually`: Manually - - **Default**: BITStoreUpdateCheckWeekly - - @warning When setting this to `BITStoreUpdateCheckManually` you need to either - invoke the update checking process yourself with `checkForUpdate` somehow, e.g. by - proving an update check button for the user or integrating the Update View into your - user interface. - @see BITStoreUpdateSetting - @see countryCode - @see checkForUpdateOnLaunch - @see checkForUpdate - */ -@property (nonatomic, assign) BITStoreUpdateSetting updateSetting; - - -/** - Defines the store country the app is always available in, otherwise uses the users locale - - If this value is not defined, then it uses the device country if the current locale. - - If you are pre-defining a country and are releasing a new version on a specific date, - it can happen that users get an alert but the update is not yet available in their country! - - But if a user downloaded the app from another appstore than the locale is set and the app is not - available in the locales app store, then the user will never receive an update notification! - - More information about possible country codes is available here: http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 - - @see updateSetting - @see checkForUpdateOnLaunch - @see checkForUpdate - */ -@property (nonatomic, copy) NSString *countryCode; - - -/** - Flag that determines whether the automatic update checks should be done. - - If this is enabled the update checks will be performed automatically depending on the - `updateSetting` property. If this is disabled the `updateSetting` property will have - no effect, and checking for updates is totally up to be done by yourself. - - *Default*: _YES_ - - @warning When setting this to `NO` you need to invoke update checks yourself! - @see updateSetting - @see countryCode - @see checkForUpdate - */ -@property (nonatomic, assign, getter=isCheckingForUpdateOnLaunch) BOOL checkForUpdateOnLaunch; - - -///----------------------------------------------------------------------------- -/// @name User Interface -///----------------------------------------------------------------------------- - - -/** - Flag that determines if the integrated update alert should be used - - If enabled, the integrated UIAlert based update notification will be used to inform - the user about a new update being available in the App Store. - - If disabled, you need to implement the `BITStoreUpdateManagerDelegate` protocol with - the method `[BITStoreUpdateManagerDelegate detectedUpdateFromStoreUpdateManager:newVersion:storeURL:]` - to be notified about new version and proceed yourself. - The manager will consider this identical to an `Ignore` user action using the alert - and not inform about this particular version any more, unless the app is updated - and this very same version shows up at a later time again as a new version. - - *Default*: _YES_ - - @warning If the HockeySDKResources bundle is missing in the application package, then the internal - update alert is also disabled and be treated identical to manually disabling this - property. - @see updateSetting - */ -@property (nonatomic, assign, getter=isUpdateUIEnabled) BOOL updateUIEnabled; - -///----------------------------------------------------------------------------- -/// @name Manual update checking -///----------------------------------------------------------------------------- - -/** - Check for an update - - Call this to trigger a check if there is a new update available on the HockeyApp servers. - - @see updateSetting - @see countryCode - @see checkForUpdateOnLaunch - */ -- (void)checkForUpdate; - - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITStoreUpdateManager.m b/submodules/HockeySDK-iOS/Classes/BITStoreUpdateManager.m deleted file mode 100644 index 52b9a4e885..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITStoreUpdateManager.m +++ /dev/null @@ -1,504 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2013-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_STORE_UPDATES - -#import - -#import "HockeySDKPrivate.h" -#import "BITHockeyHelper.h" -#import "BITHockeyHelper+Application.h" - -#import "BITHockeyBaseManagerPrivate.h" -#import "BITStoreUpdateManagerPrivate.h" - -@interface BITStoreUpdateManager () - -@property (nonatomic, copy) NSString *latestStoreVersion; -@property (nonatomic, copy) NSString *appStoreURLString; -@property (nonatomic, copy) NSString *currentUUID; -@property (nonatomic) BOOL updateAlertShowing; -@property (nonatomic) BOOL lastCheckFailed; -@property (nonatomic, weak) id appDidBecomeActiveObserver; -@property (nonatomic, weak) id networkDidBecomeReachableObserver; - -@end - -@implementation BITStoreUpdateManager - -#pragma mark - private - -- (void)reportError:(NSError *)error { - BITHockeyLogError(@"ERROR: %@", [error localizedDescription]); - self.lastCheckFailed = YES; -} - - -- (void)didBecomeActiveActions { - if ([self shouldCancelProcessing]) return; - - if ([self isCheckingForUpdateOnLaunch] && [self shouldAutoCheckForUpdates]) { - [self performSelector:@selector(checkForUpdateDelayed) withObject:nil afterDelay:1.0]; - } -} - -#pragma mark - Observers - -- (void) registerObservers { - __weak typeof(self) weakSelf = self; - if(nil == self.appDidBecomeActiveObserver) { - self.appDidBecomeActiveObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification - object:nil - queue:NSOperationQueue.mainQueue - usingBlock:^(NSNotification __unused *note) { - typeof(self) strongSelf = weakSelf; - [strongSelf didBecomeActiveActions]; - }]; - } - if(nil == self.networkDidBecomeReachableObserver) { - self.networkDidBecomeReachableObserver = [[NSNotificationCenter defaultCenter] addObserverForName:BITHockeyNetworkDidBecomeReachableNotification - object:nil - queue:NSOperationQueue.mainQueue - usingBlock:^(NSNotification __unused *note) { - typeof(self) strongSelf = weakSelf; - [strongSelf didBecomeActiveActions]; - }]; - } -} - -- (void) unregisterObservers { - id strongAppDidBecomeActiveObserver = self.appDidBecomeActiveObserver; - id strongNetworkDidBecomeReachableObserver = self.networkDidBecomeReachableObserver; - if(strongAppDidBecomeActiveObserver) { - [[NSNotificationCenter defaultCenter] removeObserver:strongAppDidBecomeActiveObserver]; - self.appDidBecomeActiveObserver = nil; - } - if(strongNetworkDidBecomeReachableObserver) { - [[NSNotificationCenter defaultCenter] removeObserver:strongNetworkDidBecomeReachableObserver]; - self.networkDidBecomeReachableObserver = nil; - } -} - - -#pragma mark - Init - -- (instancetype)init { - if ((self = [super init])) { - _checkInProgress = NO; - _updateAvailable = NO; - _lastCheckFailed = NO; - _enableStoreUpdateManager = NO; - _updateAlertShowing = NO; - _updateUIEnabled = YES; - _latestStoreVersion = nil; - _appStoreURLString = nil; - _currentUUID = [[self executableUUID] copy]; - _countryCode = nil; - - _mainBundle = [NSBundle mainBundle]; - _currentLocale = [NSLocale currentLocale]; - _userDefaults = [NSUserDefaults standardUserDefaults]; - - // set defaults - self.checkForUpdateOnLaunch = YES; - self.updateSetting = BITStoreUpdateCheckWeekly; - - if (!BITHockeyBundle()) { - BITHockeyLogWarning(@"[HockeySDK] WARNING: %@ is missing, built in UI is deactivated!", BITHOCKEYSDK_BUNDLE); - } - } - return self; -} - -- (void)dealloc { - [self unregisterObservers]; -} - - -#pragma mark - Version - -- (NSString *)lastStoreVersion { - NSString *versionString = nil; - - if ([self.userDefaults objectForKey:kBITStoreUpdateLastStoreVersion]) { - // get the last saved version string from the app store - versionString = [self.userDefaults objectForKey:kBITStoreUpdateLastStoreVersion]; - } - - // if there is a UUID saved which doesn't match the current binary UUID - // then there is possibly a newer version in the store - NSString *lastSavedUUID = nil; - if ([self.userDefaults objectForKey:kBITStoreUpdateLastUUID]) { - lastSavedUUID = [self.userDefaults objectForKey:kBITStoreUpdateLastUUID]; - - if (lastSavedUUID && [lastSavedUUID length] > 0 && ![lastSavedUUID isEqualToString:self.currentUUID]) { - // the UUIDs don't match, store the new one - [self.userDefaults setObject:self.currentUUID forKey:kBITStoreUpdateLastUUID]; - - if (versionString) { - // a new version has been installed, reset everything - // so we set versionString to nil to simulate that this is the very run - [self.userDefaults removeObjectForKey:kBITStoreUpdateLastStoreVersion]; - versionString = nil; - } - } - } - - return versionString; -} - -- (BOOL)hasNewVersion:(NSDictionary *)dictionary { - self.lastCheckFailed = YES; - - NSString *lastStoreVersion = [self lastStoreVersion]; - - if ([[dictionary objectForKey:@"results"] isKindOfClass:[NSArray class]] && - [(NSArray *)[dictionary objectForKey:@"results"] count] > 0 ) { - self.lastCheckFailed = NO; - - self.latestStoreVersion = [(NSDictionary *)[(NSArray *)[dictionary objectForKey:@"results"] objectAtIndex:0] objectForKey:@"version"]; - self.appStoreURLString = [(NSDictionary *)[(NSArray *)[dictionary objectForKey:@"results"] objectAtIndex:0] objectForKey:@"trackViewUrl"]; - - NSString *ignoredVersion = nil; - if ([self.userDefaults objectForKey:kBITStoreUpdateIgnoreVersion]) { - ignoredVersion = [self.userDefaults objectForKey:kBITStoreUpdateIgnoreVersion]; - BITHockeyLogDebug(@"INFO: Ignored version: %@", ignoredVersion); - } - - if (!self.latestStoreVersion || !self.appStoreURLString) { - return NO; - } else if (ignoredVersion && [ignoredVersion isEqualToString:self.latestStoreVersion]) { - return NO; - } else if (!lastStoreVersion) { - // this is the very first time we get a valid response and - // set the reference of the store result to be equal to the current installed version - // even though the current installed version could be older than the one in the app store - // but this ensures that we never have false alerts, since the version string in - // iTunes Connect doesn't have to match CFBundleVersion or CFBundleShortVersionString - // and even if it matches it is hard/impossible to 100% determine which one it is, - // since they could change at any time - [self.userDefaults setObject:self.currentUUID forKey:kBITStoreUpdateLastUUID]; - [self.userDefaults setObject:self.latestStoreVersion forKey:kBITStoreUpdateLastStoreVersion]; - return NO; - } else { - BITHockeyLogDebug(@"INFO: Compare new version string %@ with %@", self.latestStoreVersion, lastStoreVersion); - - NSComparisonResult comparisonResult = bit_versionCompare(self.latestStoreVersion, lastStoreVersion); - - if (comparisonResult == NSOrderedDescending) { - return YES; - } else { - return NO; - } - - } - } - - return NO; -} - - -#pragma mark - Time - -- (BOOL)shouldAutoCheckForUpdates { - BOOL checkForUpdate = NO; - - switch (self.updateSetting) { - case BITStoreUpdateCheckDaily: { - NSTimeInterval dateDiff = fabs([self.lastCheck timeIntervalSinceNow]); - if (dateDiff != 0) - dateDiff = dateDiff / (60*60*24); - - checkForUpdate = (dateDiff >= 1); - break; - } - case BITStoreUpdateCheckWeekly: { - NSTimeInterval dateDiff = fabs([self.lastCheck timeIntervalSinceNow]); - if (dateDiff != 0) - dateDiff = dateDiff / (60*60*24); - - checkForUpdate = (dateDiff >= 7); - break; - } - case BITStoreUpdateCheckManually: - checkForUpdate = NO; - break; - default: - break; - } - - return checkForUpdate; -} - - -#pragma mark - Private - -- (BOOL)shouldCancelProcessing { - if (self.appEnvironment != BITEnvironmentAppStore) { - BITHockeyLogWarning(@"WARNING: StoreUpdateManager is cancelled because it's not running in an AppStore environment"); - return YES; - } - - if (![self isStoreUpdateManagerEnabled]) { - return YES; - } - - return NO; -} - - -- (BOOL)processStoreResponseWithString:(NSString *)responseString { - if (!responseString) return NO; - - NSData *data = [responseString dataUsingEncoding:NSUTF8StringEncoding]; - - NSError *error = nil; - NSDictionary *json = (NSDictionary *)[NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; - - if (error) { - BITHockeyLogError(@"ERROR: Invalid JSON string. %@", [error localizedDescription]); - return NO; - } - - // remember that we just checked the server - self.lastCheck = [NSDate date]; - - self.updateAvailable = [self hasNewVersion:json]; - - BITHockeyLogDebug(@"INFO: Update available: %i", self.updateAvailable); - - if (self.lastCheckFailed) { - BITHockeyLogError(@"ERROR: Last check failed"); - return NO; - } - - if ([self isUpdateAvailable]) { - id strongDelegate = self.delegate; - if ([strongDelegate respondsToSelector:@selector(detectedUpdateFromStoreUpdateManager:newVersion:storeURL:)]) { - [strongDelegate detectedUpdateFromStoreUpdateManager:self newVersion:self.latestStoreVersion storeURL:[NSURL URLWithString:self.appStoreURLString]]; - } - - if (self.updateUIEnabled && BITHockeyBundle()) { - [self showUpdateAlert]; - } else { - // Ignore this version - [self.userDefaults setObject:self.latestStoreVersion forKey:kBITStoreUpdateIgnoreVersion]; - } - } - - return YES; -} - - -#pragma mark - Update Check - -- (void)checkForUpdateManual:(BOOL)manual { - if ([self shouldCancelProcessing]) return; - - if (self.isCheckInProgress) return; - self.checkInProgress = YES; - - // do we need to update? - if (!manual && ![self shouldAutoCheckForUpdates]) { - BITHockeyLogDebug(@"INFO: Update check not needed right now"); - self.checkInProgress = NO; - return; - } - - NSString *country = @""; - if (self.countryCode) { - country = [NSString stringWithFormat:@"&country=%@", self.countryCode]; - } else { - // if the local is by any chance the systemLocale, it could happen that the NSLocaleCountryCode returns nil! - if ([(NSDictionary *)self.currentLocale objectForKey:NSLocaleCountryCode]) { - country = [NSString stringWithFormat:@"&country=%@", [(NSDictionary *)self.currentLocale objectForKey:NSLocaleCountryCode]]; - } else { - // don't check, just to be save - BITHockeyLogError(@"ERROR: Locale returned nil, can't determine the store to use!"); - self.checkInProgress = NO; - return; - } - } - - NSString *appBundleIdentifier = [self.mainBundle objectForInfoDictionaryKey:@"CFBundleIdentifier"]; - - NSString *url = [NSString stringWithFormat:@"https://itunes.apple.com/lookup?bundleId=%@%@", - bit_URLEncodedString(appBundleIdentifier), - country]; - - BITHockeyLogDebug(@"INFO: Sending request to %@", url); - - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:(NSURL *)[NSURL URLWithString:url] cachePolicy:1 timeoutInterval:10.0]; - [request setHTTPMethod:@"GET"]; - [request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"]; - - __weak typeof (self) weakSelf = self; - NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; - __block NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration]; - - NSURLSessionDataTask *task = [session dataTaskWithRequest:request - completionHandler: ^(NSData *data, NSURLResponse __unused *response, NSError *error) { - typeof (self) strongSelf = weakSelf; - - [session finishTasksAndInvalidate]; - - [strongSelf handleResponeWithData:data error:error]; - }]; - [task resume]; -} - -- (void)handleResponeWithData:(NSData *)responseData error:(NSError *)error{ - self.checkInProgress = NO; - - if (error) { - [self reportError:error]; - } else if ([responseData length]) { - NSString *responseString = [[NSString alloc] initWithBytes:[responseData bytes] length:[responseData length] encoding: NSUTF8StringEncoding]; - BITHockeyLogWarning(@"INFO: Received API response: %@", responseString); - - if (!responseString || ![responseString dataUsingEncoding:NSUTF8StringEncoding]) { - return; - } - - [self processStoreResponseWithString:responseString]; - } -} - -- (void)checkForUpdateDelayed { - [self checkForUpdateManual:NO]; -} - -- (void)checkForUpdate { - [self checkForUpdateManual:YES]; -} - - -// begin the startup process -- (void)startManager { - if ([self shouldCancelProcessing]) return; - - BITHockeyLogDebug(@"INFO: Start UpdateManager"); - - if ([self.userDefaults objectForKey:kBITStoreUpdateDateOfLastCheck]) { - self.lastCheck = [self.userDefaults objectForKey:kBITStoreUpdateDateOfLastCheck]; - } - - if (!self.lastCheck) { - self.lastCheck = [NSDate distantPast]; - } - - [self registerObservers]; - - // we are already delayed, so the notification already came in and this won't invoked twice - switch ([BITHockeyHelper applicationState]) { - case BITApplicationStateActive: - [self didBecomeActiveActions]; - break; - case BITApplicationStateBackground: - case BITApplicationStateInactive: - case BITApplicationStateUnknown: - // do nothing, wait for active state - break; - } -} - - -#pragma mark - Alert - -- (void)showUpdateAlert { - dispatch_async(dispatch_get_main_queue(), ^{ - if (!self.updateAlertShowing) { - NSString *versionString = [NSString stringWithFormat:@"%@ %@", BITHockeyLocalizedString(@"Version"), self.latestStoreVersion]; - __weak typeof(self) weakSelf = self; - - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:BITHockeyLocalizedString(@"UpdateAvailable") - message:[NSString stringWithFormat:BITHockeyLocalizedString(@"UpdateAlertTextWithAppVersion"), versionString] - preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *ignoreAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"Ignore") - style:UIAlertActionStyleCancel - handler:^(UIAlertAction __unused *action) { - typeof(self) strongSelf = weakSelf; - [strongSelf ignoreAction]; - }]; - [alertController addAction:ignoreAction]; - UIAlertAction *remindAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"Remind Me") - style:UIAlertActionStyleDefault - handler:^(UIAlertAction __unused *action) { - typeof(self) strongSelf = weakSelf; - [strongSelf remindAction]; - }]; - [alertController addAction:remindAction]; - UIAlertAction *showAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"Show") - style:UIAlertActionStyleDefault - handler:^(UIAlertAction __unused *action) { - typeof(self) strongSelf = weakSelf; - [strongSelf showAction]; - }]; - [alertController addAction:showAction]; - [self showAlertController:alertController]; - self.updateAlertShowing = YES; - } - }); -} - - -#pragma mark - Properties - -- (void)setLastCheck:(NSDate *)aLastCheck { - if (_lastCheck != aLastCheck) { - _lastCheck = aLastCheck; - - [self.userDefaults setObject:self.lastCheck forKey:kBITStoreUpdateDateOfLastCheck]; - } -} - -- (void)ignoreAction { - self.updateAlertShowing = NO; - [self.userDefaults setObject:self.latestStoreVersion forKey:kBITStoreUpdateIgnoreVersion]; -} - -- (void)remindAction { - self.updateAlertShowing = NO; -} - -- (void)showAction { - self.updateAlertShowing = NO; - [self.userDefaults setObject:self.latestStoreVersion forKey:kBITStoreUpdateIgnoreVersion]; - - if (self.appStoreURLString) { - [[UIApplication sharedApplication] openURL:(NSURL *)[NSURL URLWithString:self.appStoreURLString]]; - } else { - BITHockeyLogWarning(@"WARNING: The app store page couldn't be opened, since we did not get a valid URL from the store API."); - } -} - -@end - -#endif /* HOCKEYSDK_FEATURE_STORE_UPDATES */ diff --git a/submodules/HockeySDK-iOS/Classes/BITStoreUpdateManagerDelegate.h b/submodules/HockeySDK-iOS/Classes/BITStoreUpdateManagerDelegate.h deleted file mode 100644 index 0d629dc0d5..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITStoreUpdateManagerDelegate.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2013-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import - -@class BITStoreUpdateManager; - -/** - The `BITStoreUpdateManagerDelegate` formal protocol defines methods for - more interaction with `BITStoreUpdateManager`. - */ - -@protocol BITStoreUpdateManagerDelegate - -@optional - - -///----------------------------------------------------------------------------- -/// @name Update information -///----------------------------------------------------------------------------- - -/** Informs which new version has been reported to be available - - @warning If this is invoked with a simulated new version, the storeURL could be _NIL_ if the current builds - bundle identifier is different to the bundle identifier used in the app store build. - @param storeUpdateManager The `BITStoreUpdateManager` instance invoking this delegate - @param newVersion The new version string reported by the App Store - @param storeURL The App Store URL for this app that could be invoked to let them perform the update. - */ --(void)detectedUpdateFromStoreUpdateManager:(BITStoreUpdateManager *)storeUpdateManager newVersion:(NSString *)newVersion storeURL:(NSURL *)storeURL; - - - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITStoreUpdateManagerPrivate.h b/submodules/HockeySDK-iOS/Classes/BITStoreUpdateManagerPrivate.h deleted file mode 100644 index 156913c514..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITStoreUpdateManagerPrivate.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Author: Andreas Linde - * Peter Steinberger - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * Copyright (c) 2011 Andreas Linde. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - - -#if HOCKEYSDK_FEATURE_STORE_UPDATES - -@interface BITStoreUpdateManager () { -} - -///----------------------------------------------------------------------------- -/// @name Delegate -///----------------------------------------------------------------------------- - -/** - Sets the optional `BITStoreUpdateManagerDelegate` delegate. - */ -@property (nonatomic, weak) id delegate; - - -// is an update available? -@property (nonatomic, assign, getter=isUpdateAvailable) BOOL updateAvailable; - -// are we currently checking for updates? -@property (nonatomic, assign, getter=isCheckInProgress) BOOL checkInProgress; - -@property (nonatomic, strong) NSDate *lastCheck; - -// used by BITHockeyManager if disable status is changed -@property (nonatomic, getter = isStoreUpdateManagerEnabled) BOOL enableStoreUpdateManager; - -#pragma mark - For Testing - -@property (nonatomic, strong) NSBundle *mainBundle; -@property (nonatomic, strong) NSLocale *currentLocale; -@property (nonatomic, strong) NSUserDefaults *userDefaults; - -- (BOOL)shouldAutoCheckForUpdates; -- (BOOL)hasNewVersion:(NSDictionary *)dictionary; -- (BOOL)processStoreResponseWithString:(NSString *)responseString; -- (void)checkForUpdateDelayed; - -@end - -#endif /* HOCKEYSDK_FEATURE_STORE_UPDATES */ diff --git a/submodules/HockeySDK-iOS/Classes/BITTelemetryContext.h b/submodules/HockeySDK-iOS/Classes/BITTelemetryContext.h deleted file mode 100644 index 31ef5ba742..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITTelemetryContext.h +++ /dev/null @@ -1,106 +0,0 @@ -#import "HockeySDKFeatureConfig.h" - -#if HOCKEYSDK_FEATURE_METRICS - -#import -#import "BITApplication.h" -#import "BITDevice.h" -#import "BITInternal.h" -#import "BITUser.h" -#import "BITSession.h" - -@class BITPersistence; - -#import "HockeySDKNullability.h" -NS_ASSUME_NONNULL_BEGIN - -/** - * Context object which contains information about the device, user, session etc. - */ -@interface BITTelemetryContext : NSObject - -///----------------------------------------------------------------------------- -/// @name Initialisation -///----------------------------------------------------------------------------- - -/** - * The persistence instance used to save/load metadata. - */ -@property(nonatomic, strong) BITPersistence *persistence; - -/** - * The instrumentation key of the app. - */ -@property(nonatomic, copy) NSString *appIdentifier; - -/** - * A queue which makes array operations thread safe. - */ -@property (nonatomic, strong) dispatch_queue_t operationsQueue; - -/** - * The application context. - */ -@property(nonatomic, strong, readonly) BITApplication *application; - -/** - * The device context. - */ -@property (nonatomic, strong, readonly)BITDevice *device; - -/** - * The session context. - */ -@property (nonatomic, strong, readonly)BITSession *session; - -/** - * The user context. - */ -@property (nonatomic, strong, readonly)BITUser *user; - -/** - * The internal context. - */ -@property (nonatomic, strong, readonly)BITInternal *internal; - -/** - * Initializes a telemetry context. - * - * @param appIdentifier the appIdentifier of the app - * @param persistence the persistence used to save and load metadata - * - * @return the telemetry context - */ -- (instancetype)initWithAppIdentifier:(NSString *)appIdentifier persistence:(BITPersistence *)persistence; - -///----------------------------------------------------------------------------- -/// @name Helper -///----------------------------------------------------------------------------- - -/** - * A dictionary which holds static tag fields for the purpose of caching - */ -@property (nonatomic, strong) NSDictionary *tags; - -/** - * Returns context objects as dictionary. - * - * @return a dictionary containing all context fields - */ -- (NSDictionary *)contextDictionary; - -///----------------------------------------------------------------------------- -/// @name Getter/Setter -///----------------------------------------------------------------------------- - -- (void)setSessionId:(NSString *)sessionId; - -- (void)setIsFirstSession:(NSString *)isFirstSession; - -- (void)setIsNewSession:(NSString *)isNewSession; - -@end - -NS_ASSUME_NONNULL_END - -#endif /* HOCKEYSDK_FEATURE_METRICS */ diff --git a/submodules/HockeySDK-iOS/Classes/BITTelemetryContext.m b/submodules/HockeySDK-iOS/Classes/BITTelemetryContext.m deleted file mode 100644 index b0119268a5..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITTelemetryContext.m +++ /dev/null @@ -1,380 +0,0 @@ -#import -#import "BITTelemetryContext.h" - -#if HOCKEYSDK_FEATURE_METRICS - -#import "BITMetricsManagerPrivate.h" -#import "BITHockeyHelper.h" -#import "BITPersistence.h" -#import "BITPersistencePrivate.h" - -static NSString *const kBITUserMetaData = @"BITUserMetaData"; - -static char *const BITContextOperationsQueue = "net.hockeyapp.telemetryContextQueue"; - -@implementation BITTelemetryContext - -@synthesize appIdentifier = _appIdentifier; -@synthesize persistence = _persistence; - -#pragma mark - Initialisation - --(instancetype)init { - - if((self = [super init])) { - _operationsQueue = dispatch_queue_create(BITContextOperationsQueue, DISPATCH_QUEUE_CONCURRENT); - } - return self; -} - -- (instancetype)initWithAppIdentifier:(NSString *)appIdentifier persistence:(BITPersistence *)persistence { - - if ((self = [self init])) { - _persistence = persistence; - _appIdentifier = appIdentifier; - BITDevice *deviceContext = [BITDevice new]; - deviceContext.model = bit_devicePlatform(); - deviceContext.type = bit_deviceType(); - deviceContext.osVersion = bit_osVersionBuild(); - deviceContext.os = bit_osName(); - deviceContext.deviceId = bit_appAnonID(NO); - deviceContext.locale = bit_deviceLocale(); - deviceContext.language = bit_deviceLanguage(); - deviceContext.screenResolution = bit_screenSize(); - deviceContext.oemName = @"Apple"; - - BITInternal *internalContext = [BITInternal new]; - internalContext.sdkVersion = bit_sdkVersion(); - - BITApplication *applicationContext = [BITApplication new]; - applicationContext.version = bit_appVersion(); - - BITUser *userContext = [self loadUser]; - if (!userContext) { - userContext = [self newUser]; - [self saveUser:userContext]; - } - - BITSession *sessionContext = [BITSession new]; - - _application = applicationContext; - _device = deviceContext; - _user = userContext; - _internal = internalContext; - _session = sessionContext; - _tags = [self tags]; - } - return self; -} - -- (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -#pragma mark - User - -- (BITUser *)newUser { - return ({ - BITUser *user = [BITUser new]; - user.userId = bit_appAnonID(NO); - user; - }); -} - -- (void)saveUser:(BITUser *)user{ - NSDictionary *userMetaData = @{kBITUserMetaData : user}; - [self.persistence persistMetaData:userMetaData]; -} - -- (nullable BITUser *)loadUser{ - NSDictionary *metaData =[self.persistence metaData]; - BITUser *user = [metaData objectForKey:kBITUserMetaData]; - return user; -} - -#pragma mark - Network - -#pragma mark - Getter/Setter properties - -- (NSString *)appIdentifier { - __block NSString *tmp; - dispatch_sync(self.operationsQueue, ^{ - tmp = self->_appIdentifier; - }); - return tmp; -} - -- (void)setAppIdentifier:(NSString *)appIdentifier { - NSString* tmp = [appIdentifier copy]; - dispatch_barrier_async(self.operationsQueue, ^{ - self->_appIdentifier = tmp; - }); -} - -- (NSString *)screenResolution { - __block NSString *tmp; - dispatch_sync(self.operationsQueue, ^{ - tmp = self.device.screenResolution; - }); - return tmp; -} - -- (void)setScreenResolution:(NSString *)screenResolution { - NSString* tmp = [screenResolution copy]; - dispatch_barrier_async(self.operationsQueue, ^{ - self.device.screenResolution = tmp; - }); -} - -- (NSString *)appVersion { - __block NSString *tmp; - dispatch_sync(self.operationsQueue, ^{ - tmp = self.application.version; - }); - return tmp; -} - -- (void)setAppVersion:(NSString *)appVersion { - NSString* tmp = [appVersion copy]; - dispatch_barrier_async(self.operationsQueue, ^{ - self.application.version = tmp; - }); -} - -- (NSString *)anonymousUserId { - __block NSString *tmp; - dispatch_sync(self.operationsQueue, ^{ - tmp = self.user.userId; - }); - return tmp; -} - -- (void)setAnonymousUserId:(NSString *)userId { - NSString* tmp = [userId copy]; - dispatch_barrier_async(self.operationsQueue, ^{ - self.user.userId = tmp; - }); -} - -- (NSString *)anonymousUserAquisitionDate { - __block NSString *tmp; - dispatch_sync(self.operationsQueue, ^{ - tmp = self.user.anonUserAcquisitionDate; - }); - return tmp; -} - -- (void)setAnonymousUserAquisitionDate:(NSString *)anonymousUserAquisitionDate { - NSString* tmp = [anonymousUserAquisitionDate copy]; - dispatch_barrier_async(self.operationsQueue, ^{ - self.user.anonUserAcquisitionDate = tmp; - }); -} - -- (NSString *)sdkVersion { - __block NSString *tmp; - dispatch_sync(self.operationsQueue, ^{ - tmp = self.internal.sdkVersion; - }); - return tmp; -} - -- (void)setSdkVersion:(NSString *)sdkVersion { - NSString* tmp = [sdkVersion copy]; - dispatch_barrier_async(self.operationsQueue, ^{ - self.internal.sdkVersion = tmp; - }); -} - -- (NSString *)sessionId { - __block NSString *tmp; - dispatch_sync(self.operationsQueue, ^{ - tmp = self.session.sessionId; - }); - return tmp; -} - -- (void)setSessionId:(NSString *)sessionId { - NSString* tmp = [sessionId copy]; - dispatch_barrier_async(self.operationsQueue, ^{ - self.session.sessionId = tmp; - }); -} - -- (NSString *)isFirstSession { - __block NSString *tmp; - dispatch_sync(self.operationsQueue, ^{ - tmp = self.session.isFirst; - }); - return tmp; -} - -- (void)setIsFirstSession:(NSString *)isFirstSession { - NSString* tmp = [isFirstSession copy]; - dispatch_barrier_async(self.operationsQueue, ^{ - self.session.isFirst = tmp; - }); -} - -- (NSString *)isNewSession { - __block NSString *tmp; - dispatch_sync(self.operationsQueue, ^{ - tmp = self.session.isNew; - }); - return tmp; -} - -- (void)setIsNewSession:(NSString *)isNewSession { - NSString* tmp = [isNewSession copy]; - dispatch_barrier_async(self.operationsQueue, ^{ - self.session.isNew = tmp; - }); -} - -- (NSString *)osVersion { - __block NSString *tmp; - dispatch_sync(self.operationsQueue, ^{ - tmp = self.device.osVersion; - }); - return tmp; -} - -- (void)setOsVersion:(NSString *)osVersion { - NSString* tmp = [osVersion copy]; - dispatch_barrier_async(self.operationsQueue, ^{ - self.device.osVersion = tmp; - }); -} - -- (NSString *)osName { - __block NSString *tmp; - dispatch_sync(self.operationsQueue, ^{ - tmp = self.device.os; - }); - return tmp; -} - -- (void)setOsName:(NSString *)osName { - NSString* tmp = [osName copy]; - dispatch_barrier_async(self.operationsQueue, ^{ - self.device.os = tmp; - }); -} - -- (NSString *)deviceModel { - __block NSString *tmp; - dispatch_sync(self.operationsQueue, ^{ - tmp = self.device.model; - }); - return tmp; -} - -- (void)setDeviceModel:(NSString *)deviceModel { - NSString* tmp = [deviceModel copy]; - dispatch_barrier_async(self.operationsQueue, ^{ - self.device.model = tmp; - }); -} - -- (NSString *)deviceOemName { - __block NSString *tmp; - dispatch_sync(self.operationsQueue, ^{ - tmp = self.device.oemName; - }); - return tmp; -} - -- (void)setDeviceOemName:(NSString *)oemName { - NSString* tmp = [oemName copy]; - dispatch_barrier_async(self.operationsQueue, ^{ - self.device.oemName = tmp; - }); -} - -- (NSString *)osLocale { - __block NSString *tmp; - dispatch_sync(self.operationsQueue, ^{ - tmp = self.device.locale; - }); - return tmp; -} - -- (void)setOsLocale:(NSString *)osLocale { - NSString* tmp = [osLocale copy]; - dispatch_barrier_async(self.operationsQueue, ^{ - self.device.locale = tmp; - }); -} - -- (NSString *)osLanguage { - __block NSString *tmp; - dispatch_sync(self.operationsQueue, ^{ - tmp = self.device.language; - }); - return tmp; -} - -- (void)setOsLanguage:(NSString *)osLanguage { - NSString* tmp = [osLanguage copy]; - dispatch_barrier_async(self.operationsQueue, ^{ - self.device.language = tmp; - }); -} - -- (NSString *)deviceId { - __block NSString *tmp; - dispatch_sync(self.operationsQueue, ^{ - tmp = self.device.deviceId; - }); - return tmp; -} - -- (void)setDeviceId:(NSString *)deviceId { - NSString* tmp = [deviceId copy]; - dispatch_barrier_async(self.operationsQueue, ^{ - self.device.deviceId = tmp; - }); -} - -- (NSString *)deviceType { - __block NSString *tmp; - dispatch_sync(self.operationsQueue, ^{ - tmp = self.device.type; - }); - return tmp; -} - -- (void)setDeviceType:(NSString *)deviceType { - NSString* tmp = [deviceType copy]; - dispatch_barrier_async(self.operationsQueue, ^{ - self.device.type = tmp; - }); -} - -#pragma mark - Custom getter -#pragma mark - Helper - -- (NSDictionary *)contextDictionary { - __block NSMutableDictionary *tmp = [NSMutableDictionary new]; - dispatch_sync(self.operationsQueue, ^{ - [tmp addEntriesFromDictionary:self.tags]; - [tmp addEntriesFromDictionary:[self.session serializeToDictionary]]; - [tmp addEntriesFromDictionary:[self.user serializeToDictionary]]; - }); - return tmp; -} - -- (NSDictionary *)tags { - if(!_tags){ - NSMutableDictionary *tags = [self.application serializeToDictionary].mutableCopy; - [tags addEntriesFromDictionary:[self.application serializeToDictionary]]; - [tags addEntriesFromDictionary:[self.internal serializeToDictionary]]; - [tags addEntriesFromDictionary:[self.device serializeToDictionary]]; - _tags = tags; - } - return _tags; -} - -@end - -#endif /* HOCKEYSDK_FEATURE_METRICS */ diff --git a/submodules/HockeySDK-iOS/Classes/BITTelemetryData.h b/submodules/HockeySDK-iOS/Classes/BITTelemetryData.h deleted file mode 100644 index 50539a1074..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITTelemetryData.h +++ /dev/null @@ -1,18 +0,0 @@ -#import "BITTelemetryObject.h" - -#import "HockeySDKNullability.h" -NS_ASSUME_NONNULL_BEGIN - -///Data contract class for type BITTelemetryData. -@interface BITTelemetryData : BITTelemetryObject - -@property (nonatomic, readonly, copy) NSString *envelopeTypeName; -@property (nonatomic, readonly, copy) NSString *dataTypeName; - -@property (nonatomic, copy) NSNumber *version; -@property (nonatomic, copy) NSString *name; -@property (nonatomic, strong) NSDictionary *properties; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/HockeySDK-iOS/Classes/BITTelemetryData.m b/submodules/HockeySDK-iOS/Classes/BITTelemetryData.m deleted file mode 100644 index 285ff321f5..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITTelemetryData.m +++ /dev/null @@ -1,33 +0,0 @@ -#import "BITTelemetryData.h" - -@implementation BITTelemetryData - -- (NSDictionary *)serializeToDictionary { - NSMutableDictionary *dict = [super serializeToDictionary].mutableCopy; - if (self.version != nil) { - [dict setObject:self.version forKey:@"ver"]; - } - - return dict; -} - -#pragma mark - NSCoding - -- (instancetype)initWithCoder:(NSCoder *)coder { - self = [super initWithCoder:coder]; - if(self) { - _version = (NSNumber *)[coder decodeObjectForKey:@"self.version"]; - _name = (NSString *)[coder decodeObjectForKey:@"self.name"]; - _properties = (NSDictionary *)[coder decodeObjectForKey:@"self.properties"]; - } - return self; -} - -- (void)encodeWithCoder:(NSCoder *)coder { - [super encodeWithCoder:coder]; - [coder encodeObject:self.version forKey:@"self.version"]; - [coder encodeObject:self.name forKey:@"self.name"]; - [coder encodeObject:self.properties forKey:@"self.properties"]; -} - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITTelemetryObject.h b/submodules/HockeySDK-iOS/Classes/BITTelemetryObject.h deleted file mode 100644 index 09b1dda154..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITTelemetryObject.h +++ /dev/null @@ -1,8 +0,0 @@ -#import - - -@interface BITTelemetryObject : NSObject - -- (NSDictionary *)serializeToDictionary; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITTelemetryObject.m b/submodules/HockeySDK-iOS/Classes/BITTelemetryObject.m deleted file mode 100644 index c996738696..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITTelemetryObject.m +++ /dev/null @@ -1,18 +0,0 @@ -#import "BITTelemetryObject.h" - -@implementation BITTelemetryObject - -// empty implementation for the base class -- (NSDictionary *)serializeToDictionary{ - return [NSDictionary dictionary]; -} - -- (void)encodeWithCoder:(NSCoder *) __unused coder { -} - -- (instancetype)initWithCoder:(NSCoder *) __unused coder { - return [super init]; -} - - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITUpdateManager.h b/submodules/HockeySDK-iOS/Classes/BITUpdateManager.h deleted file mode 100644 index c39cf714ff..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITUpdateManager.h +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Author: Andreas Linde - * Peter Steinberger - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * Copyright (c) 2011 Andreas Linde. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - - -#import "BITHockeyBaseManager.h" - - -/** - * Update check interval - */ -typedef NS_ENUM (NSUInteger, BITUpdateSetting) { - /** - * On every startup or or when the app comes to the foreground - */ - BITUpdateCheckStartup = 0, - /** - * Once a day - */ - BITUpdateCheckDaily = 1, - /** - * Manually - */ - BITUpdateCheckManually = 2 -}; - -@protocol BITUpdateManagerDelegate; - -@class BITAppVersionMetaInfo; -@class BITUpdateViewController; - -/** - The update manager module. - - This is the HockeySDK module for handling app updates when using Ad-Hoc or Enterprise provisioning profiles. - This module handles version updates, presents update and version information in an App Store like user interface, - collects usage information and provides additional authorization options when using Ad-Hoc provisioning profiles. - - By default, this module automatically disables itself when running in an App Store build! - - The protocol `BITUpdateManagerDelegate` provides delegates to inform about events and adjust a few behaviors. - - To use the server side restriction feature, to provide updates only to specific users, you need to setup the - `BITAuthenticator` class. This allows the update request to tell the server which user is using the app on the - current device and then let the server decide which updates the device may see. - - */ - -@interface BITUpdateManager : BITHockeyBaseManager - -///----------------------------------------------------------------------------- -/// @name Update Checking -///----------------------------------------------------------------------------- - -// see HockeyUpdateSetting-enum. Will be saved in user defaults. -// default value: HockeyUpdateCheckStartup -/** - When to check for new updates. - - Defines when the SDK should check if there is a new update available on the - server. This must be assigned one of the following, see `BITUpdateSetting`: - - - `BITUpdateCheckStartup`: On every startup or or when the app comes to the foreground - - `BITUpdateCheckDaily`: Once a day - - `BITUpdateCheckManually`: Manually - - When running the app from the App Store, this setting is ignored. - - **Default**: BITUpdateCheckStartup - - @warning When setting this to `BITUpdateCheckManually` you need to either - invoke the update checking process yourself with `checkForUpdate` somehow, e.g. by - proving an update check button for the user or integrating the Update View into your - user interface. - @see BITUpdateSetting - @see checkForUpdateOnLaunch - @see checkForUpdate - */ -@property (nonatomic, assign) BITUpdateSetting updateSetting; - - -/** - Flag that determines whether the automatic update checks should be done. - - If this is enabled the update checks will be performed automatically depending on the - `updateSetting` property. If this is disabled the `updateSetting` property will have - no effect, and checking for updates is totally up to be done by yourself. - - When running the app from the App Store, this setting is ignored. - - *Default*: _YES_ - - @warning When setting this to `NO` you need to invoke update checks yourself! - @see updateSetting - @see checkForUpdate - */ -@property (nonatomic, assign, getter=isCheckForUpdateOnLaunch) BOOL checkForUpdateOnLaunch; - - -// manually start an update check -/** - Check for an update - - Call this to trigger a check if there is a new update available on the HockeyApp servers. - - When running the app from the App Store, this method call is ignored. - - @see updateSetting - @see checkForUpdateOnLaunch - */ -- (void)checkForUpdate; - - -///----------------------------------------------------------------------------- -/// @name Update Notification -///----------------------------------------------------------------------------- - -/** - Flag that determines if update alerts should be repeatedly shown - - If enabled the update alert shows on every startup and whenever the app becomes active, - until the update is installed. - If disabled the update alert is only shown once ever and it is up to you to provide an - alternate way for the user to navigate to the update UI or update in another way. - - When running the app from the App Store, this setting is ignored. - - *Default*: _YES_ - */ -@property (nonatomic, assign) BOOL alwaysShowUpdateReminder; - - -/** - Flag that determines if the update alert should show a direct install option - - If enabled the update alert shows an additional option which allows to invoke the update - installation process directly instead of viewing the update UI first. - By default the alert only shows a `Show` and `Ignore` option. - - When running the app from the App Store, this setting is ignored. - - *Default*: _NO_ - */ -@property (nonatomic, assign, getter=isShowingDirectInstallOption) BOOL showDirectInstallOption; - - -///----------------------------------------------------------------------------- -/// @name Expiry -///----------------------------------------------------------------------------- - -/** - Expiry date of the current app version - - If set, the app will get unusable at the given date by presenting a blocking view on - top of the apps UI so that no interaction is possible. To present a custom UI, check - the documentation of the - `[BITUpdateManagerDelegate shouldDisplayExpiryAlertForUpdateManager:]` delegate. - - Once the expiry date is reached, the app will no longer check for updates or - send any usage data to the server! - - When running the app from the App Store, this setting is ignored. - - *Default*: nil - @see disableUpdateCheckOptionWhenExpired - @see [BITUpdateManagerDelegate shouldDisplayExpiryAlertForUpdateManager:] - @see [BITUpdateManagerDelegate didDisplayExpiryAlertForUpdateManager:] - @warning This only works when using Ad-Hoc provisioning profiles! - */ -@property (nonatomic, strong) NSDate *expiryDate; - -/** - Disable the update check button from expiry screen or alerts - - If do not want your users to be able to check for updates once a version is expired, - then enable this property. - - If this is not enabled, the users will be able to check for updates and install them - if any is available for the current device. - - *Default*: NO - @see expiryDate - @see [BITUpdateManagerDelegate shouldDisplayExpiryAlertForUpdateManager:] - @see [BITUpdateManagerDelegate didDisplayExpiryAlertForUpdateManager:] - @warning This only works when using Ad-Hoc provisioning profiles! -*/ -@property (nonatomic) BOOL disableUpdateCheckOptionWhenExpired; - - -///----------------------------------------------------------------------------- -/// @name User Interface -///----------------------------------------------------------------------------- - - -/** - Present the modal update user interface. - - @warning Make sure to call this method from the main thread! - */ -- (void)showUpdateView; - - -/** - Create an update view - - @param modal Return a view which is ready for modal presentation with an integrated navigation bar - @return BITUpdateViewController The update user interface view controller, - e.g. to push it onto a navigation stack. - */ -- (BITUpdateViewController *)hockeyViewController:(BOOL)modal; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITUpdateManager.m b/submodules/HockeySDK-iOS/Classes/BITUpdateManager.m deleted file mode 100644 index 5516eb2173..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITUpdateManager.m +++ /dev/null @@ -1,1152 +0,0 @@ -/* - * Author: Andreas Linde - * Peter Steinberger - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * Copyright (c) 2011 Andreas Linde. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_UPDATES - -#import - -#import "HockeySDKPrivate.h" -#import "BITHockeyHelper.h" -#import "BITHockeyHelper+Application.h" - -#import "BITHockeyBaseManagerPrivate.h" -#import "BITUpdateManagerPrivate.h" -#import "BITUpdateViewControllerPrivate.h" -#import "BITAppVersionMetaInfo.h" - -#if HOCKEYSDK_FEATURE_CRASH_REPORTER -#import "BITCrashManagerPrivate.h" -#endif - -typedef NS_ENUM(NSInteger, BITUpdateAlertViewTag) { - BITUpdateAlertViewTagDefaultUpdate = 0, - BITUpdateAlertViewTagNeverEndingAlertView = 1, - BITUpdateAlertViewTagMandatoryUpdate = 2, -}; - -@interface BITUpdateManager () - -@property (nonatomic, copy) NSString *currentAppVersion; -@property (nonatomic) BOOL dataFound; -@property (nonatomic) BOOL showFeedback; -@property (nonatomic) BOOL updateAlertShowing; -@property (nonatomic) BOOL lastCheckFailed; -@property (nonatomic, strong) NSFileManager *fileManager; -@property (nonatomic, copy) NSString *updateDir; -@property (nonatomic, copy) NSString *usageDataFile; -@property (nonatomic, weak) id appDidBecomeActiveObserver; -@property (nonatomic, weak) id appDidEnterBackgroundObserver; -@property (nonatomic, weak) id networkDidBecomeReachableObserver; -@property (nonatomic) BOOL didStartUpdateProcess; -@property (nonatomic) BOOL didEnterBackgroundState; -@property (nonatomic) BOOL firstStartAfterInstall; -@property (nonatomic, strong) NSNumber *versionID; -@property (nonatomic, copy) NSString *versionUUID; -@property (nonatomic, copy) NSString *uuid; -@property (nonatomic, copy) NSString *blockingScreenMessage; -@property (nonatomic, strong) NSDate *lastUpdateCheckFromBlockingScreen; - -@end - -@implementation BITUpdateManager - - -#pragma mark - private - -- (void)reportError:(NSError *)error { - BITHockeyLogError(@"ERROR: %@", [error localizedDescription]); - self.lastCheckFailed = YES; - - // only show error if we enable that - if (self.showFeedback) { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:BITHockeyLocalizedString(@"An Error Occurred") - message:[error localizedDescription] - preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *okAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"OK") - style:UIAlertActionStyleDefault - handler:^(UIAlertAction __unused *action) {}]; - [alertController addAction:okAction]; - [self showAlertController:alertController]; - self.showFeedback = NO; - } -} - - -- (void)didBecomeActiveActions { - if ([self isUpdateManagerDisabled]) return; - - // this is a special iOS 8 case for handling the case that the app is not moved to background - // once the users accepts the iOS install alert button. Without this, the install process doesn't start. - // - // Important: The iOS dialog offers the user to deny installation, we can't find out which button - // was tapped, so we assume the user agreed - if (self.didStartUpdateProcess) { - self.didStartUpdateProcess = NO; - id strongDelegate = self.delegate; - if ([strongDelegate respondsToSelector:@selector(updateManagerWillExitApp:)]) { - [strongDelegate updateManagerWillExitApp:self]; - } - -#if HOCKEYSDK_FEATURE_CRASH_REPORTER - [[BITHockeyManager sharedHockeyManager].crashManager leavingAppSafely]; -#endif - - // for now we simply exit the app, later SDK versions might optionally show an alert with localized text - // describing the user to press the home button to start the update process - exit(0); - } - - if (!self.didEnterBackgroundState) return; - - self.didEnterBackgroundState = NO; - - [self checkExpiryDateReached]; - if ([self expiryDateReached]) return; - - [self startUsage]; - - if ([self isCheckForUpdateOnLaunch] && [self shouldCheckForUpdates]) { - [self checkForUpdate]; - } -} - -- (void)didEnterBackgroundActions { - self.didEnterBackgroundState = NO; - - if ([BITHockeyHelper applicationState] == BITApplicationStateBackground) { - self.didEnterBackgroundState = YES; - } -} - - -#pragma mark - Observers -- (void) registerObservers { - __weak typeof(self) weakSelf = self; - if(nil == self.appDidEnterBackgroundObserver) { - self.appDidEnterBackgroundObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification - object:nil - queue:NSOperationQueue.mainQueue - usingBlock:^(NSNotification __unused *note) { - typeof(self) strongSelf = weakSelf; - [strongSelf didEnterBackgroundActions]; - }]; - } - if(nil == self.appDidBecomeActiveObserver) { - self.appDidBecomeActiveObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification - object:nil - queue:NSOperationQueue.mainQueue - usingBlock:^(NSNotification __unused *note) { - typeof(self) strongSelf = weakSelf; - [strongSelf didBecomeActiveActions]; - }]; - } - if(nil == self.networkDidBecomeReachableObserver) { - self.networkDidBecomeReachableObserver = [[NSNotificationCenter defaultCenter] addObserverForName:BITHockeyNetworkDidBecomeReachableNotification - object:nil - queue:NSOperationQueue.mainQueue - usingBlock:^(NSNotification __unused *note) { - typeof(self) strongSelf = weakSelf; - [strongSelf didBecomeActiveActions]; - }]; - } -} - -- (void) unregisterObservers { - id strongDidEnterBackgroundObserver = self.appDidEnterBackgroundObserver; - id strongDidBecomeActiveObserver = self.appDidBecomeActiveObserver; - id strongNetworkDidBecomeReachableObserver = self.networkDidBecomeReachableObserver; - if(strongDidEnterBackgroundObserver) { - [[NSNotificationCenter defaultCenter] removeObserver:strongDidEnterBackgroundObserver]; - self.appDidEnterBackgroundObserver = nil; - } - if(strongDidBecomeActiveObserver) { - [[NSNotificationCenter defaultCenter] removeObserver:strongDidBecomeActiveObserver]; - self.appDidBecomeActiveObserver = nil; - } - if(strongNetworkDidBecomeReachableObserver) { - [[NSNotificationCenter defaultCenter] removeObserver:strongNetworkDidBecomeReachableObserver]; - self.networkDidBecomeReachableObserver = nil; - } -} - - -#pragma mark - Expiry - -- (BOOL)expiryDateReached { - if (self.appEnvironment != BITEnvironmentOther) return NO; - - if (self.expiryDate) { - NSDate *currentDate = [NSDate date]; - if ([currentDate compare:self.expiryDate] != NSOrderedAscending) - return YES; - } - - return NO; -} - -- (void)checkExpiryDateReached { - if (![self expiryDateReached]) return; - - BOOL shouldShowDefaultAlert = YES; - id strongDelegate = self.delegate; - if ([strongDelegate respondsToSelector:@selector(shouldDisplayExpiryAlertForUpdateManager:)]) { - shouldShowDefaultAlert = [strongDelegate shouldDisplayExpiryAlertForUpdateManager:self]; - } - - if (shouldShowDefaultAlert) { - NSString *appName = bit_appName(BITHockeyLocalizedString(@"Telegram")); - if (!self.blockingScreenMessage) - self.blockingScreenMessage = [NSString stringWithFormat:BITHockeyLocalizedString(@"Update expired"), appName]; - [self showBlockingScreen:self.blockingScreenMessage image:@"authorize_denied.png"]; - - if ([strongDelegate respondsToSelector:@selector(didDisplayExpiryAlertForUpdateManager:)]) { - [strongDelegate didDisplayExpiryAlertForUpdateManager:self]; - } - - // the UI is now blocked, make sure we don't add our UI on top of it over and over again - [self unregisterObservers]; - } -} - -#pragma mark - Usage - -- (void)loadAppVersionUsageData { - self.currentAppVersionUsageTime = @0; - - if ([self expiryDateReached]) return; - - BOOL newVersion = NO; - - if (![[NSUserDefaults standardUserDefaults] valueForKey:kBITUpdateUsageTimeForUUID]) { - newVersion = YES; - } else { - if ([(NSString *)[[NSUserDefaults standardUserDefaults] valueForKey:kBITUpdateUsageTimeForUUID] compare:self.uuid] != NSOrderedSame) { - newVersion = YES; - } - } - - if (newVersion) { - [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithDouble:[[NSDate date] timeIntervalSinceReferenceDate]] forKey:kBITUpdateDateOfVersionInstallation]; - [[NSUserDefaults standardUserDefaults] setObject:self.uuid forKey:kBITUpdateUsageTimeForUUID]; - [self storeUsageTimeForCurrentVersion:[NSNumber numberWithDouble:0]]; - } else { - if (![self.fileManager fileExistsAtPath:self.usageDataFile]) - return; - - NSData *codedData = [[NSData alloc] initWithContentsOfFile:self.usageDataFile]; - if (codedData == nil) return; - - NSKeyedUnarchiver *unarchiver = nil; - - @try { - unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:codedData]; - } - @catch (NSException __unused *exception) { - return; - } - - if ([unarchiver containsValueForKey:kBITUpdateUsageTimeOfCurrentVersion]) { - self.currentAppVersionUsageTime = [unarchiver decodeObjectForKey:kBITUpdateUsageTimeOfCurrentVersion]; - } - - [unarchiver finishDecoding]; - } -} - -- (void)startUsage { - if ([self expiryDateReached]) return; - - self.usageStartTimestamp = [NSDate date]; -} - -- (void)stopUsage { - if (self.appEnvironment != BITEnvironmentOther) return; - if ([self expiryDateReached]) return; - - double timeDifference = [[NSDate date] timeIntervalSinceReferenceDate] - [self.usageStartTimestamp timeIntervalSinceReferenceDate]; - double previousTimeDifference = [self.currentAppVersionUsageTime doubleValue]; - - [self storeUsageTimeForCurrentVersion:[NSNumber numberWithDouble:previousTimeDifference + timeDifference]]; -} - -- (void) storeUsageTimeForCurrentVersion:(NSNumber *)usageTime { - if (self.appEnvironment != BITEnvironmentOther) return; - - NSMutableData *data = [[NSMutableData alloc] init]; - NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; - - [archiver encodeObject:usageTime forKey:kBITUpdateUsageTimeOfCurrentVersion]; - - [archiver finishEncoding]; - [data writeToFile:self.usageDataFile atomically:YES]; - - self.currentAppVersionUsageTime = usageTime; -} - -- (NSString *)currentUsageString { - double currentUsageTime = [self.currentAppVersionUsageTime doubleValue]; - - if (currentUsageTime > 0) { - // round (up) to 1 minute - return [NSString stringWithFormat:@"%.0f", ceil(currentUsageTime / 60.0)*60]; - } else { - return @"0"; - } -} - -- (NSString *)installationDateString { - NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; - [formatter setDateFormat:@"MM/dd/yyyy"]; - double installationTimeStamp = [[NSUserDefaults standardUserDefaults] doubleForKey:kBITUpdateDateOfVersionInstallation]; - if (installationTimeStamp == 0.0) { - return [formatter stringFromDate:[NSDate date]]; - } else { - return [formatter stringFromDate:[NSDate dateWithTimeIntervalSinceReferenceDate:installationTimeStamp]]; - } -} - - -#pragma mark - Cache - -- (void)checkUpdateAvailable { - // check if there is an update available - NSComparisonResult comparisonResult = bit_versionCompare(self.newestAppVersion.version, self.currentAppVersion); - - if (comparisonResult == NSOrderedDescending) { - self.updateAvailable = YES; - } else if (comparisonResult == NSOrderedSame) { - // compare using the binary UUID and stored version id - self.updateAvailable = NO; - if (self.firstStartAfterInstall) { - if ([self.newestAppVersion hasUUID:self.uuid]) { - self.versionUUID = [self.uuid copy]; - self.versionID = [self.newestAppVersion.versionID copy]; - [self saveAppCache]; - } else { - [self.appVersions enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { - if (idx > 0 && [obj isKindOfClass:[BITAppVersionMetaInfo class]]) { - NSComparisonResult compareVersions = bit_versionCompare([(BITAppVersionMetaInfo *)obj version], self.currentAppVersion); - BOOL uuidFound = [(BITAppVersionMetaInfo *)obj hasUUID:self.uuid]; - - if (uuidFound) { - self.versionUUID = [self.uuid copy]; - self.versionID = [[(BITAppVersionMetaInfo *)obj versionID] copy]; - [self saveAppCache]; - - self.updateAvailable = YES; - } - - if (compareVersions != NSOrderedSame || uuidFound) { - *stop = YES; - } - } - }]; - } - } else { - if ([self.newestAppVersion.versionID compare:self.versionID] == NSOrderedDescending) - self.updateAvailable = YES; - } - } -} - -- (void)loadAppCache { - self.firstStartAfterInstall = NO; - self.versionUUID = [[NSUserDefaults standardUserDefaults] objectForKey:kBITUpdateInstalledUUID]; - if (!self.versionUUID) { - self.firstStartAfterInstall = YES; - } else { - if ([self.uuid compare:self.versionUUID] != NSOrderedSame) - self.firstStartAfterInstall = YES; - } - self.versionID = [[NSUserDefaults standardUserDefaults] objectForKey:kBITUpdateInstalledVersionID]; - self.companyName = [[NSUserDefaults standardUserDefaults] objectForKey:kBITUpdateCurrentCompanyName]; - - NSData *savedHockeyData = [[NSUserDefaults standardUserDefaults] objectForKey:kBITUpdateArrayOfLastCheck]; - NSArray *savedHockeyCheck = nil; - if (savedHockeyData) { - savedHockeyCheck = [NSKeyedUnarchiver unarchiveObjectWithData:savedHockeyData]; - } - if (savedHockeyCheck) { - self.appVersions = [NSArray arrayWithArray:savedHockeyCheck]; - [self checkUpdateAvailable]; - } else { - self.appVersions = nil; - } -} - -- (void)saveAppCache { - if (self.companyName) { - [[NSUserDefaults standardUserDefaults] setObject:self.companyName forKey:kBITUpdateCurrentCompanyName]; - } - if (self.versionUUID) { - [[NSUserDefaults standardUserDefaults] setObject:self.versionUUID forKey:kBITUpdateInstalledUUID]; - } - if (self.versionID) { - [[NSUserDefaults standardUserDefaults] setObject:self.versionID forKey:kBITUpdateInstalledVersionID]; - } - NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.appVersions]; - [[NSUserDefaults standardUserDefaults] setObject:data forKey:kBITUpdateArrayOfLastCheck]; -} - - -#pragma mark - Init - -- (instancetype)init { - if ((self = [super init])) { - _delegate = nil; - _expiryDate = nil; - _checkInProgress = NO; - _dataFound = NO; - _updateAvailable = NO; - _lastCheckFailed = NO; - _currentAppVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]; - _blockingView = nil; - _lastCheck = nil; - _uuid = [[self executableUUID] copy]; - _versionUUID = nil; - _versionID = nil; - _sendUsageData = YES; - _disableUpdateManager = NO; - _firstStartAfterInstall = NO; - _companyName = nil; - _currentAppVersionUsageTime = @0; - - // set defaults - _showDirectInstallOption = NO; - _alwaysShowUpdateReminder = YES; - _checkForUpdateOnLaunch = YES; - _updateSetting = BITUpdateCheckStartup; - - if ([[NSUserDefaults standardUserDefaults] objectForKey:kBITUpdateDateOfLastCheck]) { - // we did write something else in the past, so for compatibility reasons do this - id tempLastCheck = [[NSUserDefaults standardUserDefaults] objectForKey:kBITUpdateDateOfLastCheck]; - if ([tempLastCheck isKindOfClass:[NSDate class]]) { - _lastCheck = tempLastCheck; - } - } - - if (!_lastCheck) { - _lastCheck = [NSDate distantPast]; - } - - if (!BITHockeyBundle()) { - BITHockeyLogWarning(@"[HockeySDK] WARNING: %@ is missing, make sure it is added!", BITHOCKEYSDK_BUNDLE); - } - - _fileManager = [[NSFileManager alloc] init]; - - _usageDataFile = [bit_settingsDir() stringByAppendingPathComponent:BITHOCKEY_USAGE_DATA]; - - [self loadAppCache]; - - _installationIdentification = [self stringValueFromKeychainForKey:kBITUpdateInstallationIdentification]; - - [self loadAppVersionUsageData]; - [self startUsage]; - - NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter]; - [dnc addObserver:self selector:@selector(stopUsage) name:UIApplicationWillTerminateNotification object:nil]; - [dnc addObserver:self selector:@selector(stopUsage) name:UIApplicationWillResignActiveNotification object:nil]; - } - return self; -} - -- (void)dealloc { - [self unregisterObservers]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillTerminateNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil]; -} - - -#pragma mark - BetaUpdateUI - -- (BITUpdateViewController *)hockeyViewController:(BOOL)modal { - if (self.appEnvironment != BITEnvironmentOther) { - BITHockeyLogWarning(@"[HockeySDK] This should not be called from an app store build!"); - // return an empty view controller instead - BITHockeyBaseViewController *blankViewController = [[BITHockeyBaseViewController alloc] initWithModalStyle:modal]; - return (BITUpdateViewController *)blankViewController; - } - return [[BITUpdateViewController alloc] initWithModalStyle:modal]; -} - -- (void)showUpdateView { - if (self.appEnvironment != BITEnvironmentOther) { - BITHockeyLogWarning(@"[HockeySDK] This should not be called from an app store build!"); - return; - } - - if (self.currentHockeyViewController) { - BITHockeyLogDebug(@"INFO: Update view already visible, aborting"); - return; - } - - BITUpdateViewController *updateViewController = [self hockeyViewController:YES]; - if ([self hasNewerMandatoryVersion] || [self expiryDateReached]) { - [updateViewController setMandatoryUpdate: YES]; - } - dispatch_async(dispatch_get_main_queue(), ^{ - [self showView:updateViewController]; - }); -} - - -- (void)showCheckForUpdateAlert { - if (self.appEnvironment != BITEnvironmentOther) return; - if ([self isUpdateManagerDisabled]) return; - id strongDelegate = self.delegate; - if ([strongDelegate respondsToSelector:@selector(shouldDisplayUpdateAlertForUpdateManager:forShortVersion:forVersion:)] && - ![strongDelegate shouldDisplayUpdateAlertForUpdateManager:self forShortVersion:[self.newestAppVersion shortVersion] forVersion:[self.newestAppVersion version]]) { - return; - } - - if (!self.updateAlertShowing) { - NSString *title = BITHockeyLocalizedString(@"Update Available"); - NSString *message = [NSString stringWithFormat:BITHockeyLocalizedString(@"UpdateAlertMandatoryTextWithAppVersion"), [self.newestAppVersion nameAndVersionString]]; - if ([self hasNewerMandatoryVersion]) { - __weak typeof(self) weakSelf = self; - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title - message:message - preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *showAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"Show") - style:UIAlertActionStyleDefault - handler:^(UIAlertAction __unused *action) { - typeof(self) strongSelf = weakSelf; - self.updateAlertShowing = NO; - if (strongSelf.blockingView) { - [strongSelf.blockingView removeFromSuperview]; - } - [strongSelf showUpdateView]; - }]; - [alertController addAction:showAction]; - UIAlertAction *installAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"Install") - style:UIAlertActionStyleDefault - handler:^(UIAlertAction __unused *action) { - typeof(self) strongSelf = weakSelf; - self.updateAlertShowing = NO; - (void)[strongSelf initiateAppDownload]; - }]; - [alertController addAction:installAction]; - [self showAlertController:alertController]; - self.updateAlertShowing = YES; - } else { - message = [NSString stringWithFormat:BITHockeyLocalizedString(@"An update is available"), [self.newestAppVersion nameAndVersionString]]; - __weak typeof(self) weakSelf = self; - - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title - message:message - preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *ignoreAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"Ignore") - style:UIAlertActionStyleCancel - handler:^(UIAlertAction __unused *action) { - typeof(self) strongSelf = weakSelf; - self.updateAlertShowing = NO; - if ([strongSelf expiryDateReached] && !strongSelf.blockingView) { - [strongSelf alertFallback:self.blockingScreenMessage]; - } - }]; - [alertController addAction:ignoreAction]; - UIAlertAction *showAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"Show") - style:UIAlertActionStyleDefault - handler:^(UIAlertAction __unused *action) { - typeof(self) strongSelf = weakSelf; - self.updateAlertShowing = NO; - if (strongSelf.blockingView) { - [strongSelf.blockingView removeFromSuperview]; - } - [strongSelf showUpdateView]; - }]; - [alertController addAction:showAction]; - if (self.isShowingDirectInstallOption) { - UIAlertAction *installAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"Install") - style:UIAlertActionStyleDefault - handler:^(UIAlertAction __unused *action) { - typeof(self) strongSelf = weakSelf; - self.updateAlertShowing = NO; - (void)[strongSelf initiateAppDownload]; - }]; - [alertController addAction:installAction]; - } - [self showAlertController:alertController ]; - self.updateAlertShowing = YES; - } - } -} - - -// open an authorization screen -- (void)showBlockingScreen:(NSString *)message image:(NSString *)image { - self.blockingView = nil; - - UIWindow *visibleWindow = [self findVisibleWindow]; - if (visibleWindow == nil) { - [self alertFallback:message]; - return; - } - - CGRect frame = [visibleWindow frame]; - - self.blockingView = [[UIView alloc] initWithFrame:frame]; - UIImageView *backgroundView = [[UIImageView alloc] initWithImage:bit_imageNamed(@"bg.png", BITHOCKEYSDK_BUNDLE)]; - backgroundView.contentMode = UIViewContentModeScaleAspectFill; - backgroundView.frame = frame; - [self.blockingView addSubview:backgroundView]; - - if (image != nil) { - UIImageView *imageView = [[UIImageView alloc] initWithImage:bit_imageNamed(image, BITHOCKEYSDK_BUNDLE)]; - imageView.contentMode = UIViewContentModeCenter; - imageView.frame = frame; - [self.blockingView addSubview:imageView]; - } - - if (!self.disableUpdateCheckOptionWhenExpired) { - UIButton *checkForUpdateButton = [UIButton buttonWithType:kBITButtonTypeSystem]; - checkForUpdateButton.frame = CGRectMake((frame.size.width - 140) / (CGFloat)2.0, frame.size.height - 100, 140, 25); - [checkForUpdateButton setTitle:BITHockeyLocalizedString(@"Check") forState:UIControlStateNormal]; - [checkForUpdateButton addTarget:self - action:@selector(checkForUpdateForExpiredVersion) - forControlEvents:UIControlEventTouchUpInside]; - [self.blockingView addSubview:checkForUpdateButton]; - } - - if (message != nil) { - frame.origin.x = 20; - frame.origin.y = frame.size.height - 180; - frame.size.width -= 40; - frame.size.height = 70; - - UILabel *label = [[UILabel alloc] initWithFrame:frame]; - label.text = message; - label.textAlignment = NSTextAlignmentCenter; - label.numberOfLines = 3; - label.adjustsFontSizeToFitWidth = YES; - label.backgroundColor = [UIColor clearColor]; - - [self.blockingView addSubview:label]; - } - - [visibleWindow addSubview:self.blockingView]; -} - -- (void)checkForUpdateForExpiredVersion { - if (!self.checkInProgress) { - - if (!self.lastUpdateCheckFromBlockingScreen || - fabs([NSDate timeIntervalSinceReferenceDate] - [self.lastUpdateCheckFromBlockingScreen timeIntervalSinceReferenceDate]) > 60) { - self.lastUpdateCheckFromBlockingScreen = [NSDate date]; - [self checkForUpdateShowFeedback:NO]; - } - } -} - -// nag the user with neverending alerts if we cannot find out the window for presenting the covering sheet -- (void)alertFallback:(NSString *)message { - __weak typeof(self) weakSelf = self; - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil - message:message - preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *okAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"OK") - style:UIAlertActionStyleDefault - handler:^(UIAlertAction __unused *action) { - typeof(self) strongSelf = weakSelf; - [strongSelf alertFallback:self.blockingScreenMessage]; - }]; - [alertController addAction:okAction]; - if (!self.disableUpdateCheckOptionWhenExpired && [message isEqualToString:self.blockingScreenMessage]) { - UIAlertAction *checkAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"Check") - style:UIAlertActionStyleDefault - handler:^(UIAlertAction __unused *action) { - typeof(self) strongSelf = weakSelf; - [strongSelf checkForUpdateForExpiredVersion]; - }]; - [alertController addAction:checkAction]; - } - [self showAlertController:alertController]; -} - -#pragma mark - RequestComments - -- (BOOL)shouldCheckForUpdates { - BOOL checkForUpdate = NO; - - switch (self.updateSetting) { - case BITUpdateCheckStartup: - checkForUpdate = YES; - break; - case BITUpdateCheckDaily: { - NSTimeInterval dateDiff = fabs([self.lastCheck timeIntervalSinceNow]); - if (dateDiff != 0) - dateDiff = dateDiff / (60*60*24); - - checkForUpdate = (dateDiff >= 1); - break; - } - case BITUpdateCheckManually: - checkForUpdate = NO; - break; - } - - return checkForUpdate; -} - -- (void)checkForUpdate { - if ((self.appEnvironment == BITEnvironmentOther) && ![self isUpdateManagerDisabled]) { - if ([self expiryDateReached]) return; - if (![self installationIdentified]) return; - - if (self.isUpdateAvailable && [self hasNewerMandatoryVersion]) { - [self showCheckForUpdateAlert]; - } - - [self checkForUpdateShowFeedback:NO]; - } -} - -- (void)checkForUpdateShowFeedback:(BOOL)feedback { - if (self.appEnvironment != BITEnvironmentOther) return; - if (self.isCheckInProgress) return; - - self.showFeedback = feedback; - self.checkInProgress = YES; - - // do we need to update? - if (!self.currentHockeyViewController && ![self shouldCheckForUpdates] && self.updateSetting != BITUpdateCheckManually) { - BITHockeyLogDebug(@"INFO: Update not needed right now"); - self.checkInProgress = NO; - return; - } - - NSURLRequest *request = [self requestForUpdateCheck]; - NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:(id)self delegateQueue:nil]; - - NSURLSessionDataTask *sessionTask = [session dataTaskWithRequest:request]; - if (!sessionTask) { - self.checkInProgress = NO; - [self reportError:[NSError errorWithDomain:kBITUpdateErrorDomain - code:BITUpdateAPIClientCannotCreateConnection - userInfo:@{NSLocalizedDescriptionKey : @"Url Connection could not be created."}]]; - } else { - [sessionTask resume]; - } -} - -- (NSURLRequest *)requestForUpdateCheck { - NSString *path = [NSString stringWithFormat:@"api/2/apps/%@", self.appIdentifier]; - NSString *urlEncodedPath = [path stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLPathAllowedCharacterSet]]; - - NSMutableString *parameters = [NSMutableString stringWithFormat:@"?format=json&extended=true&sdk=%@&sdk_version=%@&uuid=%@", - BITHOCKEY_NAME, - BITHOCKEY_VERSION, - self.uuid]; - - // add installationIdentificationType and installationIdentifier if available - if (self.installationIdentification && self.installationIdentificationType) { - [parameters appendFormat:@"&%@=%@", - self.installationIdentificationType, - self.installationIdentification - ]; - } - - // add additional statistics if user didn't disable flag - if (self.sendUsageData) { - [parameters appendFormat:@"&app_version=%@&os=iOS&os_version=%@&device=%@&lang=%@&first_start_at=%@&usage_time=%@", - [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"], - [[UIDevice currentDevice] systemVersion], - [self getDevicePlatform], - [[[NSBundle mainBundle] preferredLocalizations] objectAtIndex:0], - [self installationDateString], - [self currentUsageString] - ]; - } - NSString *urlEncodedParameters = [parameters stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; - - // build request & send - NSString *url = [NSString stringWithFormat:@"%@%@%@", self.serverURL, urlEncodedPath, urlEncodedParameters]; - BITHockeyLogDebug(@"INFO: Sending api request to %@", url); - - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:(NSURL *)[NSURL URLWithString:url] - cachePolicy:NSURLRequestReloadIgnoringLocalCacheData - timeoutInterval:10.0]; - [request setHTTPMethod:@"GET"]; - [request setValue:@"Hockey/iOS" forHTTPHeaderField:@"User-Agent"]; - [request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"]; - - return request; -} - -- (BOOL)initiateAppDownload { - if (self.appEnvironment != BITEnvironmentOther) return NO; - - if (!self.isUpdateAvailable) { - BITHockeyLogWarning(@"WARNING: No update available. Aborting."); - return NO; - } - -#if TARGET_OS_SIMULATOR - - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:BITHockeyLocalizedString(@"Warning") - message:BITHockeyLocalizedString(@"UpdateSimulatorMessage") - preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *okAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"OK") - style:UIAlertActionStyleDefault - handler:^(UIAlertAction __unused *action) {}]; - [alertController addAction:okAction]; - [self showAlertController:alertController]; - return NO; - -#else - - NSString *extraParameter = [NSString string]; - if (self.sendUsageData && self.installationIdentification && self.installationIdentificationType) { - extraParameter = [NSString stringWithFormat:@"&%@=%@", - bit_URLEncodedString(self.installationIdentificationType), - bit_URLEncodedString(self.installationIdentification) - ]; - } - - NSString *hockeyAPIURL = [NSString stringWithFormat:@"%@api/2/apps/%@/app_versions/%@?format=plist%@", self.serverURL, [self encodedAppIdentifier], [self.newestAppVersion.versionID stringValue], extraParameter]; - NSString *iOSUpdateURL = [NSString stringWithFormat:@"itms-services://?action=download-manifest&url=%@", bit_URLEncodedString(hockeyAPIURL)]; - - // Notify delegate of update intent before placing the call - id stronDelegate = self.delegate; - if ([stronDelegate respondsToSelector:@selector(willStartDownloadAndUpdate:)]) { - [stronDelegate willStartDownloadAndUpdate:self]; - } - - BITHockeyLogDebug(@"INFO: API Server Call: %@, calling iOS with %@", hockeyAPIURL, iOSUpdateURL); - BOOL success = [[UIApplication sharedApplication] openURL:(NSURL*)[NSURL URLWithString:iOSUpdateURL]]; - BITHockeyLogDebug(@"INFO: System returned: %d", success); - - self.didStartUpdateProcess = success; - - return success; - -#endif /* TARGET_OS_SIMULATOR */ -} - - -// begin the startup process -- (void)startManager { - if (self.appEnvironment == BITEnvironmentOther) { - if ([self isUpdateManagerDisabled]) return; - - BITHockeyLogDebug(@"INFO: Starting UpdateManager"); - id strongDelegate = self.delegate; - if ([strongDelegate respondsToSelector:@selector(updateManagerShouldSendUsageData:)]) { - self.sendUsageData = [strongDelegate updateManagerShouldSendUsageData:self]; - } - - [self checkExpiryDateReached]; - if (![self expiryDateReached]) { - if ([self isCheckForUpdateOnLaunch] && [self shouldCheckForUpdates]) { - if ([BITHockeyHelper applicationState] != BITApplicationStateActive) return; - - [self performSelector:@selector(checkForUpdate) withObject:nil afterDelay:1.0]; - } - } - } - [self registerObservers]; -} - -#pragma mark - Handle responses - -- (void)handleError:(NSError *)error { - self.receivedData = nil; - self.checkInProgress = NO; - if ([self expiryDateReached]) { - if (!self.blockingView) { - [self alertFallback:self.blockingScreenMessage]; - } - } else { - [self reportError:error]; - } -} - -- (void)finishLoading { - { - self.checkInProgress = NO; - - if ([self.receivedData length]) { - NSString *responseString = [[NSString alloc] initWithBytes:[self.receivedData bytes] length:[self.receivedData length] encoding: NSUTF8StringEncoding]; - BITHockeyLogDebug(@"INFO: Received API response: %@", responseString); - - if (!responseString || ![responseString dataUsingEncoding:NSUTF8StringEncoding]) { - self.receivedData = nil; - return; - } - - NSError *error = nil; - NSDictionary *json = (NSDictionary *)[NSJSONSerialization JSONObjectWithData:(NSData *)[responseString dataUsingEncoding:NSUTF8StringEncoding] options:0 error:&error]; - - self.companyName = (([[json valueForKey:@"company"] isKindOfClass:[NSString class]]) ? [json valueForKey:@"company"] : nil); - - if (self.appEnvironment == BITEnvironmentOther) { - NSArray *feedArray = (NSArray *)[json valueForKey:@"versions"]; - - // remember that we just checked the server - self.lastCheck = [NSDate date]; - - // server returned empty response? - if (![feedArray count]) { - BITHockeyLogDebug(@"WARNING: No versions available for download on HockeyApp."); - self.receivedData = nil; - return; - } else { - self.lastCheckFailed = NO; - } - - - NSString *currentAppCacheVersion = [[self newestAppVersion].version copy]; - - // clear cache and reload with new data - NSMutableArray *tmpAppVersions = [NSMutableArray arrayWithCapacity:[feedArray count]]; - for (NSDictionary *dict in feedArray) { - BITAppVersionMetaInfo *appVersionMetaInfo = [BITAppVersionMetaInfo appVersionMetaInfoFromDict:dict]; - if ([appVersionMetaInfo isValid]) { - // check if minOSVersion is set and this device qualifies - BOOL deviceOSVersionQualifies = YES; - if ([appVersionMetaInfo minOSVersion] && ![[appVersionMetaInfo minOSVersion] isKindOfClass:[NSNull class]]) { - NSComparisonResult comparisonResult = bit_versionCompare(appVersionMetaInfo.minOSVersion, [[UIDevice currentDevice] systemVersion]); - if (comparisonResult == NSOrderedDescending) { - deviceOSVersionQualifies = NO; - } - } - - if (deviceOSVersionQualifies) - [tmpAppVersions addObject:appVersionMetaInfo]; - } else { - [self reportError:[NSError errorWithDomain:kBITUpdateErrorDomain - code:BITUpdateAPIServerReturnedInvalidData - userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Invalid data received from server.", NSLocalizedDescriptionKey, nil]]]; - } - } - // only set if different! - if (![self.appVersions isEqualToArray:tmpAppVersions]) { - self.appVersions = [tmpAppVersions copy]; - } - [self saveAppCache]; - - [self checkUpdateAvailable]; - BOOL newVersionDiffersFromCachedVersion = ![self.newestAppVersion.version isEqualToString:currentAppCacheVersion]; - - // show alert if we are on the latest & greatest - if (self.showFeedback && !self.isUpdateAvailable) { - // use currentVersionString, as version still may differ (e.g. server: 1.2, client: 1.3) - NSString *versionString = [self currentAppVersion]; - NSString *shortVersionString = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; - shortVersionString = shortVersionString ? [NSString stringWithFormat:@"%@ ", shortVersionString] : @""; - versionString = [shortVersionString length] ? [NSString stringWithFormat:@"(%@)", versionString] : versionString; - NSString *currentVersionString = [NSString stringWithFormat:@"%@ %@ %@%@", self.newestAppVersion.name, BITHockeyLocalizedString(@"Version"), shortVersionString, versionString]; - NSString *alertMsg = [NSString stringWithFormat:BITHockeyLocalizedString(@"UpdateNoUpdateAvailableMessage"), currentVersionString]; - __weak typeof(self) weakSelf = self; - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:BITHockeyLocalizedString(@"UpdateNoUpdateAvailableTitle") - message:alertMsg - preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *okAction = [BITAlertAction actionWithTitle:BITHockeyLocalizedString(@"HockeyOK") - style:UIAlertActionStyleDefault - handler:^(UIAlertAction __unused *action) { - typeof(self) strongSelf = weakSelf; - self.updateAlertShowing = NO; - if ([strongSelf expiryDateReached] && !strongSelf.blockingView) { - [strongSelf alertFallback:self.blockingScreenMessage]; - } - }]; - [alertController addAction:okAction]; - [self showAlertController:alertController]; - } - - if (self.isUpdateAvailable && (self.alwaysShowUpdateReminder || newVersionDiffersFromCachedVersion || [self hasNewerMandatoryVersion])) { - if (self.updateAvailable && !self.currentHockeyViewController) { - [self showCheckForUpdateAlert]; - } - } - self.showFeedback = NO; - } - } else if (![self expiryDateReached]) { - [self reportError:[NSError errorWithDomain:kBITUpdateErrorDomain - code:BITUpdateAPIServerReturnedEmptyResponse - userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Server returned an empty response.", NSLocalizedDescriptionKey, nil]]]; - } - - if (!self.updateAlertShowing && [self expiryDateReached] && !self.blockingView) { - [self alertFallback:self.blockingScreenMessage]; - } - - self.receivedData = nil; - } -} - -#pragma mark - NSURLSession - -- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *) __unused task didCompleteWithError:(NSError *)error { - - dispatch_async(dispatch_get_main_queue(), ^{ - [session finishTasksAndInvalidate]; - - if(error){ - [self handleError:error]; - }else{ - [self finishLoading]; - } - }); -} - -- (void)URLSession:(NSURLSession *) __unused session dataTask:(NSURLSessionDataTask *) __unused dataTask didReceiveData:(NSData *)data { - [self.receivedData appendData:data]; -} - -- (void)URLSession:(NSURLSession *) __unused session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { - - if ([response respondsToSelector:@selector(statusCode)]) { - NSInteger statusCode = [((NSHTTPURLResponse *)response) statusCode]; - if (statusCode == 404) { - [dataTask cancel]; - NSString *errorStr = [NSString stringWithFormat:@"Hockey API received HTTP Status Code %ld", (long)statusCode]; - [self reportError:[NSError errorWithDomain:kBITUpdateErrorDomain - code:BITUpdateAPIServerReturnedInvalidStatus - userInfo:[NSDictionary dictionaryWithObjectsAndKeys:errorStr, NSLocalizedDescriptionKey, nil]]]; - if (completionHandler) { completionHandler(NSURLSessionResponseCancel); } - return; - } - if (completionHandler) { completionHandler(NSURLSessionResponseAllow);} - } - - self.receivedData = [NSMutableData data]; - [self.receivedData setLength:0]; -} - -- (void)URLSession:(NSURLSession *) __unused session task:(NSURLSessionTask *) __unused task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *))completionHandler { - NSURLRequest *newRequest = request; - if (response) { - newRequest = nil; - } - if (completionHandler) { completionHandler(newRequest); } -} - -- (BOOL)hasNewerMandatoryVersion { - BOOL result = NO; - - for (BITAppVersionMetaInfo *appVersion in self.appVersions) { - if ([appVersion.version isEqualToString:self.currentAppVersion] || bit_versionCompare(appVersion.version, self.currentAppVersion) == NSOrderedAscending) { - break; - } - - if ([appVersion.mandatory boolValue]) { - result = YES; - } - } - - return result; -} - -#pragma mark - Properties - -- (void)setCurrentHockeyViewController:(BITUpdateViewController *)aCurrentHockeyViewController { - if (_currentHockeyViewController != aCurrentHockeyViewController) { - _currentHockeyViewController = aCurrentHockeyViewController; - //HockeySDKLog(@"active hockey view controller: %@", aCurrentHockeyViewController); - } -} - -- (NSString *)currentAppVersion { - return _currentAppVersion; -} - -- (void)setLastCheck:(NSDate *)aLastCheck { - if (_lastCheck != aLastCheck) { - _lastCheck = [aLastCheck copy]; - - [[NSUserDefaults standardUserDefaults] setObject:_lastCheck forKey:kBITUpdateDateOfLastCheck]; - } -} - -- (void)setAppVersions:(NSArray *)anAppVersions { - if (_appVersions != anAppVersions || !_appVersions) { - [self willChangeValueForKey:@"appVersions"]; - - // populate with default values (if empty) - if (![anAppVersions count]) { - BITAppVersionMetaInfo *defaultApp = [[BITAppVersionMetaInfo alloc] init]; - defaultApp.name = bit_appName(BITHockeyLocalizedString(@"HockeyAppNamePlaceholder")); - defaultApp.version = self.currentAppVersion; - defaultApp.shortVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; - _appVersions = [NSArray arrayWithObject:defaultApp]; - } else { - _appVersions = [anAppVersions copy]; - } - [self didChangeValueForKey:@"appVersions"]; - } -} - -- (BITAppVersionMetaInfo *)newestAppVersion { - BITAppVersionMetaInfo *appVersion = [self.appVersions objectAtIndex:0]; - return appVersion; -} - -- (void)setBlockingView:(UIView *)anBlockingView { - if (_blockingView != anBlockingView) { - [_blockingView removeFromSuperview]; - _blockingView = anBlockingView; - } -} - -- (void)setInstallationIdentificationType:(NSString *)installationIdentificationType { - if (![_installationIdentificationType isEqualToString:installationIdentificationType]) { - // we already use "uuid" in our requests for providing the binary UUID to the server - // so we need to stick to "udid" even when BITAuthenticator is providing a plain uuid - if ([installationIdentificationType isEqualToString:@"uuid"]) { - _installationIdentificationType = @"udid"; - } else { - _installationIdentificationType = installationIdentificationType; - } - } -} - -- (void)setInstallationIdentification:(NSString *)installationIdentification { - if (![_installationIdentification isEqualToString:installationIdentification]) { - if (installationIdentification) { - [self addStringValueToKeychain:installationIdentification forKey:kBITUpdateInstallationIdentification]; - } else { - [self removeKeyFromKeychain:kBITUpdateInstallationIdentification]; - } - _installationIdentification = installationIdentification; - - // we need to reset the usage time, because the user/device may have changed - [self storeUsageTimeForCurrentVersion:[NSNumber numberWithDouble:0]]; - self.usageStartTimestamp = [NSDate date]; - } -} - -@end - -#endif /* HOCKEYSDK_FEATURE_UPDATES */ diff --git a/submodules/HockeySDK-iOS/Classes/BITUpdateManagerDelegate.h b/submodules/HockeySDK-iOS/Classes/BITUpdateManagerDelegate.h deleted file mode 100644 index 80c1506138..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITUpdateManagerDelegate.h +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import - -@class BITUpdateManager; - -/** - The `BITUpdateManagerDelegate` formal protocol defines methods further configuring - the behaviour of `BITUpdateManager`. - */ - -@protocol BITUpdateManagerDelegate - -@optional - -///----------------------------------------------------------------------------- -/// @name Update -///----------------------------------------------------------------------------- - -/** - Return if update alert should be shown - - If you want to display your own user interface when there is an update available, - implement this method, present your user interface and return _NO_. In this case - it is your responsibility to call `BITUpdateManager showUpdateView` - - Note: This delegate will be invoked on startup and every time the app becomes - active again! - - When returning _YES_ the default blocking UI will be shown. - - When running the app from the App Store, this delegate is ignored. - - @param updateManager The `BITUpdateManager` instance invoking this delegate - @param shortVersion The latest available version - @param version The latest available version - */ -- (BOOL)shouldDisplayUpdateAlertForUpdateManager:(BITUpdateManager *)updateManager forShortVersion:(NSString *)shortVersion forVersion:(NSString *)version; - -///----------------------------------------------------------------------------- -/// @name Expiry -///----------------------------------------------------------------------------- - -/** - Return if expiry alert should be shown if date is reached - - If you want to display your own user interface when the expiry date is reached, - implement this method, present your user interface and return _NO_. In this case - it is your responsibility to make the app unusable! - - Note: This delegate will be invoked on startup and every time the app becomes - active again! - - When returning _YES_ the default blocking UI will be shown. - - When running the app from the App Store, this delegate is ignored. - - @param updateManager The `BITUpdateManager` instance invoking this delegate - @see [BITUpdateManager expiryDate] - @see [BITUpdateManagerDelegate didDisplayExpiryAlertForUpdateManager:] - */ -- (BOOL)shouldDisplayExpiryAlertForUpdateManager:(BITUpdateManager *)updateManager; - - -/** - Invoked once a default expiry alert is shown - - Once expiry date is reached and the default blocking UI is shown, - this delegate method is invoked to provide you the possibility to do any - desired additional processing. - - @param updateManager The `BITUpdateManager` instance invoking this delegate - @see [BITUpdateManager expiryDate] - @see [BITUpdateManagerDelegate shouldDisplayExpiryAlertForUpdateManager:] - */ -- (void)didDisplayExpiryAlertForUpdateManager:(BITUpdateManager *)updateManager; - - -///----------------------------------------------------------------------------- -/// @name Privacy -///----------------------------------------------------------------------------- - -/** Return NO if usage data should not be send - - The update module send usage data by default, if the application is _NOT_ - running in an App Store version. Implement this delegate and - return NO if you want to disable this. - - If you intend to implement a user setting to let them enable or disable - sending usage data, this delegate should be used to return that value. - - Usage data contains the following information: - - App Version - - iOS Version - - Device type - - Language - - Installation timestamp - - Usage time - - @param updateManager The `BITUpdateManager` instance invoking this delegate - @warning When setting this to `NO`, you will _NOT_ know if this user is actually testing! - */ -- (BOOL)updateManagerShouldSendUsageData:(BITUpdateManager *)updateManager; - - -///----------------------------------------------------------------------------- -/// @name Privacy -///----------------------------------------------------------------------------- - -/** Implement this method to be notified before an update starts. - - The update manager will send this delegate message _just_ before the system - call to update the application is placed, but after the user has already chosen - to install the update. - - There is no guarantee that the update will actually start after this delegate - message is sent. - - @param updateManager The `BITUpdateManager` instance invoking this delegate - */ -- (BOOL)willStartDownloadAndUpdate:(BITUpdateManager *)updateManager; - -/** - Invoked right before the app will exit to allow app update to start (>= iOS8 only) - - The iOS installation mechanism only starts if the app the should be updated is currently - not running. On all iOS versions up to iOS 7, the system did automatically exit the app - in these cases. Since iOS 8 this isn't done any longer. - - @param updateManager The `BITUpdateManager` instance invoking this delegate - */ -- (void)updateManagerWillExitApp:(BITUpdateManager *)updateManager; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITUpdateManagerPrivate.h b/submodules/HockeySDK-iOS/Classes/BITUpdateManagerPrivate.h deleted file mode 100644 index f578247a0a..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITUpdateManagerPrivate.h +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Author: Andreas Linde - * Peter Steinberger - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * Copyright (c) 2011 Andreas Linde. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_UPDATES - -/** TODO: - * if during startup the auth-state is pending, we get never rid of the nag-alertview - */ -@interface BITUpdateManager () - -///----------------------------------------------------------------------------- -/// @name Delegate -///----------------------------------------------------------------------------- - -/** - Sets the `BITUpdateManagerDelegate` delegate. - - The delegate is automatically set by using `[BITHockeyManager setDelegate:]`. You - should not need to set this delegate individually. - - @see `[BITHockeyManager setDelegate:]` - */ -@property (nonatomic, weak) id delegate; - - -// is an update available? -@property (nonatomic, assign, getter=isUpdateAvailable) BOOL updateAvailable; - -// are we currently checking for updates? -@property (nonatomic, assign, getter=isCheckInProgress) BOOL checkInProgress; - -@property (nonatomic, strong) NSMutableData *receivedData; - -@property (nonatomic, copy) NSDate *lastCheck; - -// get array of all available versions -@property (nonatomic, copy) NSArray *appVersions; - -@property (nonatomic, strong) NSNumber *currentAppVersionUsageTime; - -@property (nonatomic, copy) NSDate *usageStartTimestamp; - -@property (nonatomic, strong) UIView *blockingView; - -@property (nonatomic, copy) NSString *companyName; - -@property (nonatomic, copy) NSString *installationIdentification; - -@property (nonatomic, copy) NSString *installationIdentificationType; - -@property (nonatomic) BOOL installationIdentified; - -// used by BITHockeyManager if disable status is changed -@property (nonatomic, getter = isUpdateManagerDisabled) BOOL disableUpdateManager; - -// checks for update, informs the user (error, no update found, etc) -- (void)checkForUpdateShowFeedback:(BOOL)feedback; - -- (NSURLRequest *)requestForUpdateCheck; - -// initiates app-download call. displays an system UIAlertController -- (BOOL)initiateAppDownload; - -// get/set current active hockey view controller -@property (nonatomic, strong) BITUpdateViewController *currentHockeyViewController; - -@property(nonatomic) BOOL sendUsageData; - -// convenience method to get current running version string -- (NSString *)currentAppVersion; - -// get newest app version -- (BITAppVersionMetaInfo *)newestAppVersion; - -// check if there is any newer version mandatory -- (BOOL)hasNewerMandatoryVersion; - -@end - -#endif /* HOCKEYSDK_FEATURE_UPDATES */ diff --git a/submodules/HockeySDK-iOS/Classes/BITUpdateViewController.h b/submodules/HockeySDK-iOS/Classes/BITUpdateViewController.h deleted file mode 100644 index e36f318270..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITUpdateViewController.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Author: Andreas Linde - * Peter Steinberger - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * Copyright (c) 2011 Andreas Linde, Peter Steinberger. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import - -#import "BITHockeyBaseViewController.h" - - -@interface BITUpdateViewController : BITHockeyBaseViewController -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITUpdateViewController.m b/submodules/HockeySDK-iOS/Classes/BITUpdateViewController.m deleted file mode 100644 index f480a7a15b..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITUpdateViewController.m +++ /dev/null @@ -1,540 +0,0 @@ -/* - * Author: Andreas Linde - * Peter Steinberger - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * Copyright (c) 2011 Andreas Linde, Peter Steinberger. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_UPDATES - -#import "HockeySDKPrivate.h" -#import -#import "BITHockeyHelper.h" -#import "BITAppVersionMetaInfo.h" -#import "BITAppStoreHeader.h" -#import "BITWebTableViewCell.h" -#import "BITStoreButton.h" - -#import "BITUpdateManagerPrivate.h" -#import "BITUpdateViewControllerPrivate.h" -#import "BITHockeyBaseManagerPrivate.h" - - -#define kWebCellIdentifier @"PSWebTableViewCell" -#define kAppStoreViewHeight 99 - -@interface BITUpdateViewController () - -@property (nonatomic) BOOL kvoRegistered; -@property (nonatomic) BOOL showAllVersions; -@property (nonatomic, strong) BITAppStoreHeader *appStoreHeader; -@property (nonatomic, strong) BITStoreButton *appStoreButton; -@property (nonatomic, strong) id popOverController; -@property (nonatomic, strong) NSMutableArray *cells; -@property (nonatomic) BITEnvironment appEnvironment; - -@end - -@implementation BITUpdateViewController - -#pragma mark - Private - -- (UIColor *)backgroundColor { - return BIT_RGBCOLOR(255, 255, 255); -} - -- (void)restoreStoreButtonStateAnimated:(BOOL)animated { - if (self.appEnvironment == BITEnvironmentAppStore) { - [self setAppStoreButtonState:AppStoreButtonStateOffline animated:animated]; - } else if ([self.updateManager isUpdateAvailable]) { - [self setAppStoreButtonState:AppStoreButtonStateUpdate animated:animated]; - } else { - [self setAppStoreButtonState:AppStoreButtonStateCheck animated:animated]; - } -} - -- (void)updateAppStoreHeader { - BITUpdateManager *strongManager = self.updateManager; - BITAppVersionMetaInfo *appVersion = strongManager.newestAppVersion; - self.appStoreHeader.headerText = appVersion.name; - self.appStoreHeader.subHeaderText = strongManager.companyName; -} - -- (void)appDidBecomeActive { - if (self.appStoreButtonState == AppStoreButtonStateInstalling) { - [self setAppStoreButtonState:AppStoreButtonStateUpdate animated:YES]; - } else if (![self.updateManager isCheckInProgress]) { - [self restoreStoreButtonStateAnimated:YES]; - } -} - -- (UIImage *)addGlossToImage:(UIImage *)image { - UIGraphicsBeginImageContextWithOptions(image.size, NO, 0.0); - - [image drawAtPoint:CGPointZero]; - UIImage *iconGradient = bit_imageNamed(@"IconGradient.png", BITHOCKEYSDK_BUNDLE); - [iconGradient drawInRect:CGRectMake(0, 0, image.size.width, image.size.height) blendMode:kCGBlendModeNormal alpha:0.5]; - - UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - - return result; -} - -#define kMinPreviousVersionButtonHeight 50 -- (void)realignPreviousVersionButton { - - // manually collect actual table height size - NSUInteger tableViewContentHeight = 0; - for (int i=0; i < [self tableView:self.tableView numberOfRowsInSection:0]; i++) { - tableViewContentHeight += [self tableView:self.tableView heightForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]]; - } - tableViewContentHeight += self.tableView.tableHeaderView.frame.size.height; - tableViewContentHeight += self.navigationController.navigationBar.frame.size.height; - tableViewContentHeight += [UIApplication sharedApplication].statusBarFrame.size.height; - - NSUInteger footerViewSize = kMinPreviousVersionButtonHeight; - NSUInteger frameHeight = (NSUInteger)self.view.frame.size.height; - if(tableViewContentHeight < frameHeight && (frameHeight - tableViewContentHeight > 100)) { - footerViewSize = frameHeight - tableViewContentHeight; - } - - // update footer view - if(self.tableView.tableFooterView) { - CGRect frame = self.tableView.tableFooterView.frame; - frame.size.height = footerViewSize; - self.tableView.tableFooterView.frame = frame; - } -} - -- (void)changePreviousVersionButtonBackground:(id)sender { - [(UIButton *)sender setBackgroundColor:BIT_RGBCOLOR(245, 245, 245)]; -} - -- (void)changePreviousVersionButtonBackgroundHighlighted:(id)sender { - [(UIButton *)sender setBackgroundColor:BIT_RGBCOLOR(245, 245, 245)]; -} - -- (UIImage *)gradientButtonHighlightImage { - CGFloat width = 10; - CGFloat height = 70; - - CGSize size = CGSizeMake(width, height); - UIGraphicsBeginImageContextWithOptions(size, NO, 0); - CGContextRef context = UIGraphicsGetCurrentContext(); - - CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB(); - - NSArray *colors = [NSArray arrayWithObjects:(id)BIT_RGBCOLOR(69, 127, 247).CGColor, (id)BIT_RGBCOLOR(58, 68, 233).CGColor, nil]; - CGGradientRef gradient = CGGradientCreateWithColors(CGColorGetColorSpace((__bridge CGColorRef)[colors objectAtIndex:0]), (__bridge CFArrayRef)colors, (CGFloat[2]){0, 1}); - CGPoint top = CGPointMake(width / 2, 0); - CGPoint bottom = CGPointMake(width / 2, height); - CGContextDrawLinearGradient(context, gradient, top, bottom, 0); - - UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext(); - - CGGradientRelease(gradient); - CGColorSpaceRelease(colorspace); - UIGraphicsEndImageContext(); - - return theImage; -} - -- (void)showHidePreviousVersionsButton { - BOOL multipleVersionButtonNeeded = [self.updateManager.appVersions count] > 1 && !self.showAllVersions; - - if(multipleVersionButtonNeeded) { - // align at the bottom if tableview is small - UIView *footerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, kMinPreviousVersionButtonHeight)]; - footerView.autoresizingMask = UIViewAutoresizingFlexibleWidth; - footerView.backgroundColor = BIT_RGBCOLOR(245, 245, 245); - UIView *lineView1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 1)]; - lineView1.backgroundColor = BIT_RGBCOLOR(214, 214, 214); - lineView1.autoresizingMask = UIViewAutoresizingFlexibleWidth; - [footerView addSubview:lineView1]; - UIView *lineView2 = [[UIView alloc] initWithFrame:CGRectMake(0, 1, self.view.frame.size.width, 1)]; - lineView2.backgroundColor = BIT_RGBCOLOR(221, 221, 221); - lineView2.autoresizingMask = UIViewAutoresizingFlexibleWidth; - [footerView addSubview:lineView2]; - UIView *lineView3 = [[UIView alloc] initWithFrame:CGRectMake(0, 1, self.view.frame.size.width, 1)]; - lineView3.backgroundColor = BIT_RGBCOLOR(255, 255, 255); - lineView3.autoresizingMask = UIViewAutoresizingFlexibleWidth; - [footerView addSubview:lineView3]; - UIButton *footerButton = [UIButton buttonWithType:UIButtonTypeCustom]; - //footerButton.layer.shadowOffset = CGSizeMake(-2, 2); - footerButton.layer.shadowColor = [[UIColor blackColor] CGColor]; - footerButton.layer.shadowRadius = 2.0; - footerButton.titleLabel.font = [UIFont boldSystemFontOfSize:14]; - [footerButton setTitle:BITHockeyLocalizedString(@"UpdateShowPreviousVersions") forState:UIControlStateNormal]; - [footerButton setTitleColor:BIT_RGBCOLOR(61, 61, 61) forState:UIControlStateNormal]; - [footerButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted]; - [footerButton setBackgroundImage:[self gradientButtonHighlightImage] forState:UIControlStateHighlighted]; - footerButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin; - [footerButton addTarget:self action:@selector(showPreviousVersionAction) forControlEvents:UIControlEventTouchUpInside]; - footerButton.frame = CGRectMake(0, kMinPreviousVersionButtonHeight-44, self.view.frame.size.width, 44); - footerButton.backgroundColor = BIT_RGBCOLOR(245, 245, 245); - [footerView addSubview:footerButton]; - self.tableView.tableFooterView = footerView; - [self realignPreviousVersionButton]; - } else { - self.tableView.tableFooterView = nil; - self.tableView.backgroundColor = [self backgroundColor]; - } -} - -- (void)configureWebCell:(BITWebTableViewCell *)cell forAppVersion:(BITAppVersionMetaInfo *)appVersion { - // create web view for a version - NSMutableString *dateAndSizeString = [NSMutableString string]; - if (appVersion.date) { - [dateAndSizeString appendString:[appVersion dateString]]; - } - if (appVersion.size) { - if ([dateAndSizeString length]) { - [dateAndSizeString appendString:@" - "]; - } - [dateAndSizeString appendString:appVersion.sizeInMB]; - } - - NSString *installed = @""; - BITUpdateManager *strongManager = self.updateManager; - if ([appVersion.version isEqualToString:[strongManager currentAppVersion]]) { - installed = [NSString stringWithFormat:@"%@", BITHockeyLocalizedString(@"UpdateInstalled")]; - } - - if ([appVersion isEqual:strongManager.newestAppVersion]) { - if ([appVersion.notes length] > 0) { - cell.webViewContent = [NSString stringWithFormat:@"

%@%@
%@

%@

", [appVersion versionString], installed, dateAndSizeString, appVersion.notes]; - } else { - cell.webViewContent = [NSString stringWithFormat:@"
%@
", BITHockeyLocalizedString(@"UpdateNoReleaseNotesAvailable")]; - } - } else { - cell.webViewContent = [NSString stringWithFormat:@"

%@%@
%@

%@

", [appVersion versionString], installed, dateAndSizeString, [appVersion notesOrEmptyString]]; - } - cell.cellBackgroundColor = [self backgroundColor]; - [cell addWebView]; - // hack - cell.textLabel.text = @""; - - [cell addObserver:self forKeyPath:@"webViewSize" options:0 context:nil]; -} - - -#pragma mark - Init - -- (instancetype)initWithStyle:(UITableViewStyle) __unused style { - if ((self = [super initWithStyle:UITableViewStylePlain])) { - self.updateManager = [BITHockeyManager sharedHockeyManager].updateManager ; - self.appEnvironment = [BITHockeyManager sharedHockeyManager].appEnvironment; - - self.title = BITHockeyLocalizedString(@"UpdateScreenTitle"); - - self.cells = [[NSMutableArray alloc] initWithCapacity:5]; - self.popOverController = nil; - } - return self; -} - -- (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self]; - BITUpdateManager *strongManager = self.updateManager; - // test if KVO's are registered. if class is destroyed before it was shown(viewDidLoad) no KVOs are registered. - if (self.kvoRegistered) { - [strongManager removeObserver:self forKeyPath:@"checkInProgress"]; - [strongManager removeObserver:self forKeyPath:@"isUpdateURLOffline"]; - [strongManager removeObserver:self forKeyPath:@"updateAvailable"]; - [strongManager removeObserver:self forKeyPath:@"appVersions"]; - self.kvoRegistered = NO; - } - - for (UITableViewCell *cell in self.cells) { - [cell removeObserver:self forKeyPath:@"webViewSize"]; - } - -} - - -#pragma mark - View lifecycle - -- (void)viewDidLoad { - [super viewDidLoad]; - - // add notifications only to loaded view - NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter]; - [dnc addObserver:self selector:@selector(appDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil]; - - // hook into manager with kvo! - BITUpdateManager *strongManager = self.updateManager; - [strongManager addObserver:self forKeyPath:@"checkInProgress" options:0 context:nil]; - [strongManager addObserver:self forKeyPath:@"isUpdateURLOffline" options:0 context:nil]; - [strongManager addObserver:self forKeyPath:@"updateAvailable" options:0 context:nil]; - [strongManager addObserver:self forKeyPath:@"appVersions" options:0 context:nil]; - self.kvoRegistered = YES; - - self.tableView.backgroundColor = BIT_RGBCOLOR(245, 245, 245); - self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; - - UIView *topView = [[UIView alloc] initWithFrame:CGRectMake(0, -(600-kAppStoreViewHeight), self.view.frame.size.width, 600)]; - topView.autoresizingMask = UIViewAutoresizingFlexibleWidth; - topView.backgroundColor = BIT_RGBCOLOR(245, 245, 245); - [self.tableView addSubview:topView]; - - self.appStoreHeader = [[BITAppStoreHeader alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, kAppStoreViewHeight)]; - [self updateAppStoreHeader]; - - NSString *iconFilename = bit_validAppIconFilename([NSBundle mainBundle], [NSBundle mainBundle]); - if (iconFilename) { - self.appStoreHeader.iconImage = [UIImage imageNamed:iconFilename]; - } - - self.tableView.tableHeaderView = self.appStoreHeader; - - BITStoreButton *storeButton = [[BITStoreButton alloc] initWithPadding:CGPointMake(5, 58) style:BITStoreButtonStyleOS7]; - storeButton.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin; - storeButton.buttonDelegate = self; - [self.tableView.tableHeaderView addSubview:storeButton]; - storeButton.buttonData = [BITStoreButtonData dataWithLabel:@"" enabled:NO]; - [storeButton alignToSuperview]; - self.appStoreButton = storeButton; - self.appStoreButtonState = AppStoreButtonStateCheck; -} - -- (void)viewWillAppear:(BOOL)animated { - if (self.appEnvironment != BITEnvironmentOther) { - self.appStoreButtonState = AppStoreButtonStateOffline; - } else if (self.mandatoryUpdate) { - self.navigationItem.leftBarButtonItem = nil; - } - self.updateManager.currentHockeyViewController = self; - [super viewWillAppear:animated]; - [self redrawTableView]; -} - -- (void)viewWillDisappear:(BOOL)animated { - self.updateManager.currentHockeyViewController = nil; - //if the popover is still visible, dismiss it - [self.popOverController dismissPopoverAnimated:YES]; - [super viewWillDisappear:animated]; -} - -- (void)redrawTableView { - [self restoreStoreButtonStateAnimated:NO]; - [self updateAppStoreHeader]; - - // clean up and remove any pending observers - for (UITableViewCell *cell in self.cells) { - [cell removeObserver:self forKeyPath:@"webViewSize"]; - } - [self.cells removeAllObjects]; - - int i = 0; - BOOL breakAfterThisAppVersion = NO; - BITUpdateManager *stronManager = self.updateManager; - for (BITAppVersionMetaInfo *appVersion in stronManager.appVersions) { - i++; - - // only show the newer version of the app by default, if we don't show all versions - if (!self.showAllVersions) { - if ([appVersion.version isEqualToString:[stronManager currentAppVersion]]) { - if (i == 1) { - breakAfterThisAppVersion = YES; - } else { - break; - } - } - } - - BITWebTableViewCell *cell = [self webCellWithAppVersion:appVersion]; - [self.cells addObject:cell]; - - if (breakAfterThisAppVersion) break; - } - - [self.tableView reloadData]; - [self showHidePreviousVersionsButton]; -} - -- (BITWebTableViewCell *)webCellWithAppVersion:(BITAppVersionMetaInfo *)appVersion { - BITWebTableViewCell *cell = [[BITWebTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kWebCellIdentifier]; - [self configureWebCell:cell forAppVersion:appVersion]; - return cell; -} - -- (void)showPreviousVersionAction { - self.showAllVersions = YES; - BOOL showAllPending = NO; - BITUpdateManager *strongManager = self.updateManager; - for (BITAppVersionMetaInfo *appVersion in strongManager.appVersions) { - if (!showAllPending) { - if ([appVersion.version isEqualToString:[strongManager currentAppVersion]]) { - showAllPending = YES; - if (appVersion == strongManager.newestAppVersion) { - continue; // skip this version already if it the latest version is the installed one - } - } else { - continue; // skip already shown - } - } - - [self.cells addObject:[self webCellWithAppVersion:appVersion]]; - } - [self.tableView reloadData]; - [self showHidePreviousVersionsButton]; -} - - -#pragma mark - Table view data source - -- (NSInteger)numberOfSectionsInTableView:(UITableView *) __unused tableView { - return 1; -} - -- (CGFloat)tableView:(UITableView *) __unused tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { - CGFloat rowHeight = 0; - - if ([self.cells count] > (NSUInteger)indexPath.row) { - BITWebTableViewCell *cell = [self.cells objectAtIndex:indexPath.row]; - rowHeight = cell.webViewSize.height; - } - - if ([self.updateManager.appVersions count] > 1 && !self.showAllVersions) { - self.tableView.backgroundColor = BIT_RGBCOLOR(245, 245, 245); - } - - if (rowHeight == 0) { - rowHeight = indexPath.row == 0 ? 250 : 44; // fill screen on startup - self.tableView.backgroundColor = [self backgroundColor]; - } - - return rowHeight; -} - -- (NSInteger)tableView:(UITableView *) __unused tableView numberOfRowsInSection:(NSInteger) __unused section { - NSInteger cellCount = [self.cells count]; - return cellCount; -} - - -#pragma mark - KVO - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id) __unused object change:(NSDictionary *) __unused change context:(void *) __unused context { - // only make changes if we are visible - if(self.view.window) { - if ([keyPath isEqualToString:@"webViewSize"]) { - [self.tableView reloadData]; - [self realignPreviousVersionButton]; - } else if ([keyPath isEqualToString:@"checkInProgress"]) { - if (self.updateManager.isCheckInProgress) { - [self setAppStoreButtonState:AppStoreButtonStateSearching animated:YES]; - }else { - [self restoreStoreButtonStateAnimated:YES]; - } - } else if ([keyPath isEqualToString:@"isUpdateURLOffline"]) { - [self restoreStoreButtonStateAnimated:YES]; - } else if ([keyPath isEqualToString:@"updateAvailable"]) { - [self restoreStoreButtonStateAnimated:YES]; - } else if ([keyPath isEqualToString:@"appVersions"]) { - [self redrawTableView]; - } - } -} - -// Customize the appearance of table view cells. -- (UITableViewCell *)tableView:(UITableView *) __unused tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - if ([self.cells count] > (NSUInteger)indexPath.row) { - return [self.cells objectAtIndex:indexPath.row]; - } else { - BITHockeyLogWarning(@"Warning: cells_ and indexPath do not match? forgot calling redrawTableView? Returning empty UITableViewCell"); - return [UITableViewCell new]; - - } -} - - -#pragma mark - Rotation - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation) __unused interfaceOrientation duration:(NSTimeInterval) __unused duration { - // update all cells - [self.cells makeObjectsPerformSelector:@selector(addWebView)]; -} -#pragma clang diagnostic pop - -#pragma mark - PSAppStoreHeaderDelegate - -- (void)setAppStoreButtonState:(AppStoreButtonState)anAppStoreButtonState { - [self setAppStoreButtonState:anAppStoreButtonState animated:NO]; -} - -- (void)setAppStoreButtonState:(AppStoreButtonState)anAppStoreButtonState animated:(BOOL)animated { - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdirect-ivar-access" - _appStoreButtonState = anAppStoreButtonState; -#pragma clang diagnostic pop - - switch (anAppStoreButtonState) { - case AppStoreButtonStateOffline: - [self.appStoreButton setButtonData:[BITStoreButtonData dataWithLabel:BITHockeyLocalizedString(@"UpdateButtonOffline") enabled:NO] animated:animated]; - break; - case AppStoreButtonStateCheck: - [self.appStoreButton setButtonData:[BITStoreButtonData dataWithLabel:BITHockeyLocalizedString(@"UpdateButtonCheck") enabled:YES] animated:animated]; - break; - case AppStoreButtonStateSearching: - [self.appStoreButton setButtonData:[BITStoreButtonData dataWithLabel:BITHockeyLocalizedString(@"UpdateButtonSearching") enabled:NO] animated:animated]; - break; - case AppStoreButtonStateUpdate: - [self.appStoreButton setButtonData:[BITStoreButtonData dataWithLabel:BITHockeyLocalizedString(@"Update") enabled:YES] animated:animated]; - break; - case AppStoreButtonStateInstalling: - [self.appStoreButton setButtonData:[BITStoreButtonData dataWithLabel:BITHockeyLocalizedString(@"Installing") enabled:NO] animated:animated]; - break; - default: - break; - } -} - -- (void)storeButtonFired:(BITStoreButton *) __unused button { - BITUpdateManager *strongManager = self.updateManager; - switch (self.appStoreButtonState) { - case AppStoreButtonStateCheck: - [strongManager checkForUpdateShowFeedback:YES]; - break; - case AppStoreButtonStateUpdate: - if ([strongManager initiateAppDownload]) { - [self setAppStoreButtonState:AppStoreButtonStateInstalling animated:YES]; - }; - break; - default: - break; - } -} - -@end - -#endif /* HOCKEYSDK_FEATURE_UPDATES */ diff --git a/submodules/HockeySDK-iOS/Classes/BITUpdateViewControllerPrivate.h b/submodules/HockeySDK-iOS/Classes/BITUpdateViewControllerPrivate.h deleted file mode 100644 index 26747a8288..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITUpdateViewControllerPrivate.h +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Author: Andreas Linde - * Peter Steinberger - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * Copyright (c) 2011 Andreas Linde, Peter Steinberger. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_UPDATES - -#import "BITStoreButton.h" - -/** - * Button states - */ -typedef NS_ENUM(NSUInteger, AppStoreButtonState) { - /** - * Offline - */ - AppStoreButtonStateOffline, - /** - * Check - */ - AppStoreButtonStateCheck, - /** - * Searching - */ - AppStoreButtonStateSearching, - /** - * Update - */ - AppStoreButtonStateUpdate, - /** - * Installing - */ - AppStoreButtonStateInstalling -}; - - -@class BITUpdateManager; - -@protocol PSStoreButtonDelegate; - -@interface BITUpdateViewController() { -} - -@property (nonatomic, weak) BITUpdateManager *updateManager; - -@property (nonatomic, readwrite) BOOL mandatoryUpdate; - -@property (nonatomic, assign) AppStoreButtonState appStoreButtonState; - -@end - -#endif /* HOCKEYSDK_FEATURE_UPDATES */ diff --git a/submodules/HockeySDK-iOS/Classes/BITUser.h b/submodules/HockeySDK-iOS/Classes/BITUser.h deleted file mode 100755 index 12450aa078..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITUser.h +++ /dev/null @@ -1,16 +0,0 @@ -#import "BITTelemetryObject.h" - -@interface BITUser : BITTelemetryObject - -@property (nonatomic, copy) NSString *accountAcquisitionDate; -@property (nonatomic, copy) NSString *accountId; -@property (nonatomic, copy) NSString *userAgent; -@property (nonatomic, copy) NSString *userId; -@property (nonatomic, copy) NSString *storeRegion; -@property (nonatomic, copy) NSString *authUserId; -@property (nonatomic, copy) NSString *anonUserAcquisitionDate; -@property (nonatomic, copy) NSString *authUserAcquisitionDate; - -- (BOOL)isEqualToUser:(BITUser *)aUser; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITUser.m b/submodules/HockeySDK-iOS/Classes/BITUser.m deleted file mode 100755 index 17effc3412..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITUser.m +++ /dev/null @@ -1,106 +0,0 @@ -#import "BITUser.h" - -/// Data contract class for type User. -@implementation BITUser - -/// -/// Adds all members of this class to a dictionary -/// @returns dictionary to which the members of this class will be added. -/// -- (NSDictionary *)serializeToDictionary { - NSMutableDictionary *dict = [super serializeToDictionary].mutableCopy; - if (self.accountAcquisitionDate != nil) { - [dict setObject:self.accountAcquisitionDate forKey:@"ai.user.accountAcquisitionDate"]; - } - if (self.accountId != nil) { - [dict setObject:self.accountId forKey:@"ai.user.accountId"]; - } - if (self.userAgent != nil) { - [dict setObject:self.userAgent forKey:@"ai.user.userAgent"]; - } - if (self.userId != nil) { - [dict setObject:self.userId forKey:@"ai.user.id"]; - } - if(self.storeRegion != nil) { - [dict setObject:self.storeRegion forKey:@"ai.user.storeRegion"]; - } - if(self.authUserId != nil) { - [dict setObject:self.authUserId forKey:@"ai.user.authUserId"]; - } - if(self.anonUserAcquisitionDate != nil) { - [dict setObject:self.anonUserAcquisitionDate forKey:@"ai.user.anonUserAcquisitionDate"]; - } - if(self.authUserAcquisitionDate != nil) { - [dict setObject:self.authUserAcquisitionDate forKey:@"ai.user.authUserAcquisitionDate"]; - } - return dict; -} - -#pragma mark - NSCoding - -- (instancetype)initWithCoder:(NSCoder *)coder { - self = [super initWithCoder:coder]; - if(self) { - _accountAcquisitionDate = [coder decodeObjectForKey:@"self.accountAcquisitionDate"]; - _accountId = [coder decodeObjectForKey:@"self.accountId"]; - _userAgent = [coder decodeObjectForKey:@"self.userAgent"]; - _userId = [coder decodeObjectForKey:@"self.userId"]; - _storeRegion = [coder decodeObjectForKey:@"self.storeRegion"]; - _authUserId = [coder decodeObjectForKey:@"self.authUserId"]; - _anonUserAcquisitionDate = [coder decodeObjectForKey:@"self.anonUserAcquisitionDate"]; - _authUserAcquisitionDate = [coder decodeObjectForKey:@"self.authUserAcquisitionDate"]; - } - - return self; -} - -- (void)encodeWithCoder:(NSCoder *)coder { - [super encodeWithCoder:coder]; - [coder encodeObject:self.accountAcquisitionDate forKey:@"self.accountAcquisitionDate"]; - [coder encodeObject:self.accountId forKey:@"self.accountId"]; - [coder encodeObject:self.userAgent forKey:@"self.userAgent"]; - [coder encodeObject:self.userId forKey:@"self.userId"]; - [coder encodeObject:self.storeRegion forKey:@"self.storeRegion"]; - [coder encodeObject:self.authUserId forKey:@"self.authUserId"]; - [coder encodeObject:self.anonUserAcquisitionDate forKey:@"self.anonUserAcquisitionDate"]; - [coder encodeObject:self.authUserAcquisitionDate forKey:@"self.authUserAcquisitionDate"]; -} - -#pragma mark - Compare - -- (BOOL)isEqualToUser:(BITUser *)aUser { - if (aUser == self) { - return YES; - } - if (!aUser || ![aUser isKindOfClass:[self class]]) { - return NO; - } - if (![self.userId isEqualToString: aUser.userId]) { - return NO; - } - if(![self.authUserId isEqualToString: aUser.authUserId]) { - return NO; - } - if (![self.accountId isEqualToString: aUser.accountId]) { - return NO; - } - if(![self.anonUserAcquisitionDate isEqualToString: aUser.anonUserAcquisitionDate]) { - return NO; - } - if(![self.authUserAcquisitionDate isEqualToString: aUser.authUserAcquisitionDate]) { - return NO; - } - if (![self.accountAcquisitionDate isEqualToString: aUser.accountAcquisitionDate]) { - return NO; - } - if (![self.userAgent isEqualToString: aUser.userAgent]) { - return NO; - } - if(![self.storeRegion isEqualToString: aUser.storeRegion]) { - return NO; - } - - return YES; -} - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITWebTableViewCell.h b/submodules/HockeySDK-iOS/Classes/BITWebTableViewCell.h deleted file mode 100644 index c1706d2fdb..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITWebTableViewCell.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Author: Andreas Linde - * Peter Steinberger - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * Copyright (c) 2011-2012 Peter Steinberger. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - - -#import -#import - -@interface BITWebTableViewCell : UITableViewCell - -@property (nonatomic, strong) UIWebView *webView; -@property (nonatomic, copy) NSString *webViewContent; -@property (nonatomic, assign) CGSize webViewSize; -@property (nonatomic, strong) UIColor *cellBackgroundColor; - -- (void)addWebView; - -@end diff --git a/submodules/HockeySDK-iOS/Classes/BITWebTableViewCell.m b/submodules/HockeySDK-iOS/Classes/BITWebTableViewCell.m deleted file mode 100644 index a7dc7c810f..0000000000 --- a/submodules/HockeySDK-iOS/Classes/BITWebTableViewCell.m +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Author: Andreas Linde - * Peter Steinberger - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * Copyright (c) 2011-2012 Peter Steinberger. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "HockeySDK.h" - -#if HOCKEYSDK_FEATURE_UPDATES - -#import "BITWebTableViewCell.h" - -@implementation BITWebTableViewCell - -static NSString* BITWebTableViewCellHtmlTemplate = @"\ -\ -\ -\ -\ -\ -\ -%@\ -\ -\ -"; - - -#pragma mark - private - -- (void)addWebView { - if(self.webViewContent) { - CGRect webViewRect = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height); - if(!self.webView) { - self.webView = [[UIWebView alloc] initWithFrame:webViewRect]; - [self addSubview:self.webView]; - self.webView.hidden = YES; - self.webView.backgroundColor = self.cellBackgroundColor; - self.webView.opaque = NO; - self.webView.delegate = self; - self.webView.autoresizingMask = UIViewAutoresizingFlexibleWidth; - - for(UIView* subView in self.webView.subviews){ - if([subView isKindOfClass:[UIScrollView class]]){ - // disable scrolling - UIScrollView *sv = (UIScrollView *)subView; - sv.scrollEnabled = NO; - sv.bounces = NO; - - // hide shadow - for (UIView* shadowView in [subView subviews]) { - if ([shadowView isKindOfClass:[UIImageView class]]) { - shadowView.hidden = YES; - } - } - } - } - } - else - self.webView.frame = webViewRect; - - NSString *deviceWidth = [NSString stringWithFormat:@"%.0f", (double)CGRectGetWidth(self.bounds)]; - - //HockeySDKLog(@"%@\n%@\%@", PSWebTableViewCellHtmlTemplate, deviceWidth, self.webViewContent); - NSString *contentHtml = [NSString stringWithFormat:BITWebTableViewCellHtmlTemplate, deviceWidth, self.webViewContent]; - [self.webView loadHTMLString:contentHtml baseURL:[NSURL URLWithString:@"about:blank"]]; - } -} - -- (void)showWebView { - self.webView.hidden = NO; - self.textLabel.text = @""; - [self setNeedsDisplay]; -} - - -- (void)removeWebView { - if(self.webView) { - self.webView.delegate = nil; - [self.webView resignFirstResponder]; - [self.webView removeFromSuperview]; - } - self.webView = nil; - [self setNeedsDisplay]; -} - - -- (void)setWebViewContent:(NSString *)aWebViewContent { - if (_webViewContent != aWebViewContent) { - _webViewContent = aWebViewContent; - - // add basic accessibility (prevents "snarfed from ivar layout") logs - self.accessibilityLabel = aWebViewContent; - } -} - - -#pragma mark - NSObject - -- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { - if((self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])) { - self.cellBackgroundColor = [UIColor clearColor]; - } - return self; -} - -- (void)dealloc { - [self removeWebView]; -} - - -#pragma mark - UIView - -- (void)setFrame:(CGRect)aFrame { - BOOL needChange = !CGRectEqualToRect(aFrame, self.frame); - [super setFrame:aFrame]; - - if (needChange) { - [self addWebView]; - } -} - - -#pragma mark - UITableViewCell - -- (void)prepareForReuse { - [self removeWebView]; - self.webViewContent = nil; - [super prepareForReuse]; -} - - -#pragma mark - UIWebViewDelegate - -- (BOOL)webView:(UIWebView *) __unused webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { - switch (navigationType) { - case UIWebViewNavigationTypeLinkClicked: - [self openURL:request.URL]; - return NO; - case UIWebViewNavigationTypeOther: - return YES; - case UIWebViewNavigationTypeBackForward: - case UIWebViewNavigationTypeFormResubmitted: - case UIWebViewNavigationTypeFormSubmitted: - case UIWebViewNavigationTypeReload: - return NO; - } -} - -- (void)webViewDidFinishLoad:(UIWebView *) __unused webView { - if(self.webViewContent) - [self showWebView]; - - CGRect frame = self.webView.frame; - frame.size.height = 1; - self.webView.frame = frame; - CGSize fittingSize = [self.webView sizeThatFits:CGSizeZero]; - frame.size = fittingSize; - self.webView.frame = frame; - - // sizeThatFits is not reliable - use javascript for optimal height - NSString *output = [self.webView stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight;"]; - self.webViewSize = CGSizeMake(fittingSize.width, [output integerValue]); -} - -#pragma mark - Helper - -- (void)openURL:(NSURL *)URL { - [[UIApplication sharedApplication] openURL:URL]; -} - -@end - -#endif /* HOCKEYSDK_FEATURE_UPDATES */ diff --git a/submodules/HockeySDK-iOS/Classes/HockeySDK.h b/submodules/HockeySDK-iOS/Classes/HockeySDK.h deleted file mode 100644 index 0c0f2c76be..0000000000 --- a/submodules/HockeySDK-iOS/Classes/HockeySDK.h +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * Copyright (c) 2011 Andreas Linde. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import - - -#if !defined (TARGET_OS_IOS) // Defined starting in iOS 9 -#define TARGET_OS_IOS 1 -#endif - - -#import "HockeySDKFeatureConfig.h" -#import "HockeySDKEnums.h" -#import "HockeySDKNullability.h" -#import "BITAlertAction.h" - -#import "BITHockeyManager.h" -#import "BITHockeyManagerDelegate.h" - -#if HOCKEYSDK_FEATURE_CRASH_REPORTER || HOCKEYSDK_FEATURE_FEEDBACK -#import "BITHockeyAttachment.h" -#endif - -#if HOCKEYSDK_FEATURE_CRASH_REPORTER -#import "BITCrashManager.h" -#import "BITCrashAttachment.h" -#import "BITCrashManagerDelegate.h" -#import "BITCrashDetails.h" -#import "BITCrashMetaData.h" -#endif /* HOCKEYSDK_FEATURE_CRASH_REPORTER */ - -#if HOCKEYSDK_FEATURE_UPDATES -#import "BITUpdateManager.h" -#import "BITUpdateManagerDelegate.h" -#import "BITUpdateViewController.h" -#endif /* HOCKEYSDK_FEATURE_UPDATES */ - -#if HOCKEYSDK_FEATURE_STORE_UPDATES -#import "BITStoreUpdateManager.h" -#import "BITStoreUpdateManagerDelegate.h" -#endif /* HOCKEYSDK_FEATURE_STORE_UPDATES */ - -#if HOCKEYSDK_FEATURE_FEEDBACK -#import "BITFeedbackManager.h" -#import "BITFeedbackManagerDelegate.h" -#import "BITFeedbackActivity.h" -#import "BITFeedbackComposeViewController.h" -#import "BITFeedbackComposeViewControllerDelegate.h" -#import "BITFeedbackListViewController.h" -#endif /* HOCKEYSDK_FEATURE_FEEDBACK */ - -#if HOCKEYSDK_FEATURE_AUTHENTICATOR -#import "BITAuthenticator.h" -#endif /* HOCKEYSDK_FEATURE_AUTHENTICATOR */ - -#if HOCKEYSDK_FEATURE_METRICS -#import "BITMetricsManager.h" -#endif /* HOCKEYSDK_FEATURE_METRICS */ - -// Notification message which HockeyManager is listening to, to retry requesting updated from the server. -// This can be used by app developers to trigger additional points where the HockeySDK can try sending -// pending crash reports or feedback messages. -// By default the SDK retries sending pending data only when the app becomes active. -#define BITHockeyNetworkDidBecomeReachableNotification @"BITHockeyNetworkDidBecomeReachable" - -extern NSString *const kBITCrashErrorDomain; -extern NSString *const kBITUpdateErrorDomain; -extern NSString *const kBITFeedbackErrorDomain; -extern NSString *const kBITAuthenticatorErrorDomain; -extern NSString *const __attribute__((unused)) kBITHockeyErrorDomain; - diff --git a/submodules/HockeySDK-iOS/Classes/HockeySDKEnums.h b/submodules/HockeySDK-iOS/Classes/HockeySDKEnums.h deleted file mode 100644 index 9caa3dbcda..0000000000 --- a/submodules/HockeySDK-iOS/Classes/HockeySDKEnums.h +++ /dev/null @@ -1,187 +0,0 @@ -// -// HockeySDKEnums.h -// HockeySDK -// -// Created by Lukas Spieß on 08/10/15. -// -// - -#ifndef HockeySDK_HockeyEnums_h -#define HockeySDK_HockeyEnums_h - -/** - * HockeySDK Log Levels - */ -typedef NS_ENUM(NSUInteger, BITLogLevel) { - /** - * Logging is disabled - */ - BITLogLevelNone = 0, - /** - * Only errors will be logged - */ - BITLogLevelError = 1, - /** - * Errors and warnings will be logged - */ - BITLogLevelWarning = 2, - /** - * Debug information will be logged - */ - BITLogLevelDebug = 3, - /** - * Logging will be very chatty - */ - BITLogLevelVerbose = 4 -}; - -typedef NSString *(^BITLogMessageProvider)(void); -typedef void (^BITLogHandler)(BITLogMessageProvider messageProvider, BITLogLevel logLevel, const char *file, const char *function, uint line); - -/** - * HockeySDK App environment - */ -typedef NS_ENUM(NSInteger, BITEnvironment) { - /** - * App has been downloaded from the AppStore - */ - BITEnvironmentAppStore = 0, - /** - * App has been downloaded from TestFlight - */ - BITEnvironmentTestFlight = 1, - /** - * App has been installed by some other mechanism. - * This could be Ad-Hoc, Enterprise, etc. - */ - BITEnvironmentOther = 99 -}; - -/** - * HockeySDK Crash Reporter error domain - */ -typedef NS_ENUM (NSInteger, BITCrashErrorReason) { - /** - * Unknown error - */ - BITCrashErrorUnknown, - /** - * API Server rejected app version - */ - BITCrashAPIAppVersionRejected, - /** - * API Server returned empty response - */ - BITCrashAPIReceivedEmptyResponse, - /** - * Connection error with status code - */ - BITCrashAPIErrorWithStatusCode -}; - -/** - * HockeySDK Update error domain - */ -typedef NS_ENUM (NSInteger, BITUpdateErrorReason) { - /** - * Unknown error - */ - BITUpdateErrorUnknown, - /** - * API Server returned invalid status - */ - BITUpdateAPIServerReturnedInvalidStatus, - /** - * API Server returned invalid data - */ - BITUpdateAPIServerReturnedInvalidData, - /** - * API Server returned empty response - */ - BITUpdateAPIServerReturnedEmptyResponse, - /** - * Authorization secret missing - */ - BITUpdateAPIClientAuthorizationMissingSecret, - /** - * No internet connection - */ - BITUpdateAPIClientCannotCreateConnection -}; - -/** - * HockeySDK Feedback error domain - */ -typedef NS_ENUM(NSInteger, BITFeedbackErrorReason) { - /** - * Unknown error - */ - BITFeedbackErrorUnknown, - /** - * API Server returned invalid status - */ - BITFeedbackAPIServerReturnedInvalidStatus, - /** - * API Server returned invalid data - */ - BITFeedbackAPIServerReturnedInvalidData, - /** - * API Server returned empty response - */ - BITFeedbackAPIServerReturnedEmptyResponse, - /** - * Authorization secret missing - */ - BITFeedbackAPIClientAuthorizationMissingSecret, - /** - * No internet connection - */ - BITFeedbackAPIClientCannotCreateConnection -}; - -/** - * HockeySDK Authenticator error domain - */ -typedef NS_ENUM(NSInteger, BITAuthenticatorReason) { - /** - * Unknown error - */ - BITAuthenticatorErrorUnknown, - /** - * Network error - */ - BITAuthenticatorNetworkError, - - /** - * API Server returned invalid response - */ - BITAuthenticatorAPIServerReturnedInvalidResponse, - /** - * Not Authorized - */ - BITAuthenticatorNotAuthorized, - /** - * Unknown Application ID (configuration error) - */ - BITAuthenticatorUnknownApplicationID, - /** - * Authorization secret missing - */ - BITAuthenticatorAuthorizationSecretMissing, - /** - * Not yet identified - */ - BITAuthenticatorNotIdentified, -}; - -/** - * HockeySDK global error domain - */ -typedef NS_ENUM(NSInteger, BITHockeyErrorReason) { - /** - * Unknown error - */ - BITHockeyErrorUnknown -}; - -#endif /* HockeySDK_HockeyEnums_h */ diff --git a/submodules/HockeySDK-iOS/Classes/HockeySDKFeatureConfig.h b/submodules/HockeySDK-iOS/Classes/HockeySDKFeatureConfig.h deleted file mode 100644 index 6808e6721c..0000000000 --- a/submodules/HockeySDK-iOS/Classes/HockeySDKFeatureConfig.h +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2013-2014 HockeyApp, Bit Stadium GmbH. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * This is the template feature config that is used for debug builds and during development. - * For the Distribution target, we are using separate configs that will be copied over in our build script. - */ - - -#ifndef HockeySDK_HockeySDKFeatureConfig_h -#define HockeySDK_HockeySDKFeatureConfig_h - -/** - * If true, include support for handling crash reports - * - * _Default_: Enabled - */ -#ifndef HOCKEYSDK_FEATURE_CRASH_REPORTER -# define HOCKEYSDK_FEATURE_CRASH_REPORTER 1 -#endif /* HOCKEYSDK_FEATURE_CRASH_REPORTER */ - - -/** - * If true, include support for managing user feedback - * - * _Default_: Enabled - */ -#ifndef HOCKEYSDK_FEATURE_FEEDBACK -# define HOCKEYSDK_FEATURE_FEEDBACK 0 -#endif /* HOCKEYSDK_FEATURE_FEEDBACK */ - - -/** - * If true, include support for informing the user about new updates pending in the App Store - * - * _Default_: Enabled - */ -#ifndef HOCKEYSDK_FEATURE_STORE_UPDATES -# define HOCKEYSDK_FEATURE_STORE_UPDATES 0 -#endif /* HOCKEYSDK_FEATURE_STORE_UPDATES */ - - -/** - * If true, include support for authentication installations for Ad-Hoc and Enterprise builds - * - * _Default_: Enabled - */ -#ifndef HOCKEYSDK_FEATURE_AUTHENTICATOR -# define HOCKEYSDK_FEATURE_AUTHENTICATOR 1 -#endif /* HOCKEYSDK_FEATURE_AUTHENTICATOR */ - - -/** - * If true, include support for handling in-app updates for Ad-Hoc and Enterprise builds - * - * _Default_: Enabled - */ -#ifndef HOCKEYSDK_FEATURE_UPDATES -# define HOCKEYSDK_FEATURE_UPDATES 1 -#endif /* HOCKEYSDK_FEATURE_UPDATES */ - - -/** - * If true, include support for auto collecting metrics data such as sessions and user - * - * _Default_: Enabled - */ -#ifndef HOCKEYSDK_FEATURE_METRICS -# define HOCKEYSDK_FEATURE_METRICS 0 -#endif /* HOCKEYSDK_FEATURE_METRICS */ - -#endif /* HockeySDK_HockeySDKFeatureConfig_h */ diff --git a/submodules/HockeySDK-iOS/Classes/HockeySDKNullability.h b/submodules/HockeySDK-iOS/Classes/HockeySDKNullability.h deleted file mode 100644 index f98032296a..0000000000 --- a/submodules/HockeySDK-iOS/Classes/HockeySDKNullability.h +++ /dev/null @@ -1,35 +0,0 @@ -// -// HockeyNullability.h -// HockeySDK -// -// Created by Andreas Linde on 12/06/15. -// -// - -#ifndef HockeySDK_HockeyNullability_h -#define HockeySDK_HockeyNullability_h - -// Define nullability fallback for backwards compatibility -#if !__has_feature(nullability) -#define NS_ASSUME_NONNULL_BEGIN -#define NS_ASSUME_NONNULL_END -#define nullable -#define nonnull -#define null_unspecified -#define null_resettable -#define _Nullable -#define _Nonnull -#define __nullable -#define __nonnull -#define __null_unspecified -#endif - -// Fallback for convenience syntax which might not be available in older SDKs -#ifndef NS_ASSUME_NONNULL_BEGIN -#define NS_ASSUME_NONNULL_BEGIN _Pragma("clang assume_nonnull begin") -#endif -#ifndef NS_ASSUME_NONNULL_END -#define NS_ASSUME_NONNULL_END _Pragma("clang assume_nonnull end") -#endif - -#endif /* HockeySDK_HockeyNullability_h */ diff --git a/submodules/HockeySDK-iOS/Classes/HockeySDKPrivate.h b/submodules/HockeySDK-iOS/Classes/HockeySDKPrivate.h deleted file mode 100644 index 800c6f71ae..0000000000 --- a/submodules/HockeySDK-iOS/Classes/HockeySDKPrivate.h +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Author: Andreas Linde - * Kent Sutherland - * - * Copyright (c) 2012-2013 HockeyApp, Bit Stadium GmbH. - * Copyright (c) 2011 Andreas Linde & Kent Sutherland. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - - -#import -#import "BITHockeyLogger.h" - -#ifndef HockeySDK_HockeySDKPrivate_h -#define HockeySDK_HockeySDKPrivate_h - -#define BITHOCKEY_NAME @"HockeySDK" -#define BITHOCKEY_IDENTIFIER @"net.hockeyapp.sdk.ios" -#define BITHOCKEY_CRASH_SETTINGS @"BITCrashManager.plist" -#define BITHOCKEY_CRASH_ANALYZER @"BITCrashManager.analyzer" - -#define BITHOCKEY_FEEDBACK_SETTINGS @"BITFeedbackManager.plist" - -#define BITHOCKEY_USAGE_DATA @"BITUpdateManager.plist" - -#define kBITHockeyMetaUserName @"BITHockeyMetaUserName" -#define kBITHockeyMetaUserEmail @"BITHockeyMetaUserEmail" -#define kBITHockeyMetaUserID @"BITHockeyMetaUserID" - -#define kBITUpdateInstalledUUID @"BITUpdateInstalledUUID" -#define kBITUpdateInstalledVersionID @"BITUpdateInstalledVersionID" -#define kBITUpdateCurrentCompanyName @"BITUpdateCurrentCompanyName" -#define kBITUpdateArrayOfLastCheck @"BITUpdateArrayOfLastCheck" -#define kBITUpdateDateOfLastCheck @"BITUpdateDateOfLastCheck" -#define kBITUpdateDateOfVersionInstallation @"BITUpdateDateOfVersionInstallation" -#define kBITUpdateUsageTimeOfCurrentVersion @"BITUpdateUsageTimeOfCurrentVersion" -#define kBITUpdateUsageTimeForUUID @"BITUpdateUsageTimeForUUID" -#define kBITUpdateInstallationIdentification @"BITUpdateInstallationIdentification" - -#define kBITStoreUpdateDateOfLastCheck @"BITStoreUpdateDateOfLastCheck" -#define kBITStoreUpdateLastStoreVersion @"BITStoreUpdateLastStoreVersion" -#define kBITStoreUpdateLastUUID @"BITStoreUpdateLastUUID" -#define kBITStoreUpdateIgnoreVersion @"BITStoreUpdateIgnoredVersion" - -#define BITHOCKEY_INTEGRATIONFLOW_TIMESTAMP @"BITIntegrationFlowStartTimestamp" - -#define BITHOCKEYSDK_BUNDLE @"HockeySDKResources.bundle" -#define BITHOCKEYSDK_URL @"https://sdk.hockeyapp.net/" - -#define BIT_RGBCOLOR(r,g,b) [UIColor colorWithRed:(CGFloat)((r)/255.0) green:(CGFloat)((g)/255.0) blue:(CGFloat)((b)/255.0) alpha:(CGFloat)1] - -NSBundle *BITHockeyBundle(void); -NSString *BITHockeyLocalizedString(NSString *stringToken); -NSString *BITHockeyMD5(NSString *str); - -#ifndef __IPHONE_11_0 -#define __IPHONE_11_0 110000 -#endif - -#ifndef TARGET_OS_SIMULATOR - - #ifdef TARGET_IPHONE_SIMULATOR - - #define TARGET_OS_SIMULATOR TARGET_IPHONE_SIMULATOR - - #else - - #define TARGET_OS_SIMULATOR 0 - - #endif /* TARGET_IPHONE_SIMULATOR */ - -#endif /* TARGET_OS_SIMULATOR */ - -#define kBITButtonTypeSystem UIButtonTypeSystem - -#endif /* HockeySDK_HockeySDKPrivate_h */ diff --git a/submodules/HockeySDK-iOS/Classes/HockeySDKPrivate.m b/submodules/HockeySDK-iOS/Classes/HockeySDKPrivate.m deleted file mode 100644 index 9cc90e5b2d..0000000000 --- a/submodules/HockeySDK-iOS/Classes/HockeySDKPrivate.m +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Author: Andreas Linde - * - * Copyright (c) 2012-2014 HockeyApp, Bit Stadium GmbH. - * Copyright (c) 2011 Andreas Linde. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#import "HockeySDK.h" -#import "HockeySDKPrivate.h" -#include - -NSString *const kBITCrashErrorDomain = @"BITCrashReporterErrorDomain"; -NSString *const kBITUpdateErrorDomain = @"BITUpdaterErrorDomain"; -NSString *const kBITFeedbackErrorDomain = @"BITFeedbackErrorDomain"; -NSString *const kBITHockeyErrorDomain = @"BITHockeyErrorDomain"; -NSString *const kBITAuthenticatorErrorDomain = @"BITAuthenticatorErrorDomain"; - -// Load the framework bundle. -NSBundle *BITHockeyBundle(void) { - static NSBundle *bundle = nil; - static dispatch_once_t predicate; - dispatch_once(&predicate, ^{ - NSString* mainBundlePath = [[NSBundle bundleForClass:[BITHockeyManager class]] resourcePath]; - NSString* frameworkBundlePath = [mainBundlePath stringByAppendingPathComponent:BITHOCKEYSDK_BUNDLE]; - bundle = [NSBundle bundleWithPath:frameworkBundlePath]; - }); - return bundle; -} - -NSString *BITHockeyLocalizedString(NSString *stringToken) { - if (!stringToken) return @""; - - NSString *appSpecificLocalizationString = NSLocalizedString(stringToken, @""); - if (appSpecificLocalizationString && ![stringToken isEqualToString:appSpecificLocalizationString]) { - return appSpecificLocalizationString; - } else if (BITHockeyBundle()) { - NSString *bundleSpecificLocalizationString = NSLocalizedStringFromTableInBundle(stringToken, @"HockeySDK", BITHockeyBundle(), @""); - if (bundleSpecificLocalizationString) - return bundleSpecificLocalizationString; - return stringToken; - } else { - return stringToken; - } -} - -NSString *BITHockeyMD5(NSString *str) { - NSData *utf8Bytes = [str dataUsingEncoding:NSUTF8StringEncoding]; - unsigned char result[CC_MD5_DIGEST_LENGTH] = {0}; - CC_MD5( utf8Bytes.bytes, (CC_LONG)utf8Bytes.length, result ); - return [NSString - stringWithFormat: @"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", - result[0], result[1], - result[2], result[3], - result[4], result[5], - result[6], result[7], - result[8], result[9], - result[10], result[11], - result[12], result[13], - result[14], result[15] - ]; -} diff --git a/submodules/HockeySDK-iOS/Documentation/Guides/App Versioning.md b/submodules/HockeySDK-iOS/Documentation/Guides/App Versioning.md deleted file mode 100644 index f404d6a834..0000000000 --- a/submodules/HockeySDK-iOS/Documentation/Guides/App Versioning.md +++ /dev/null @@ -1,45 +0,0 @@ -## Introduction - -We suggest to handle beta and release versions in two separate *apps* on HockeyApp with their own bundle identifier (e.g. by adding "beta" to the bundle identifier), so - -* both apps can run on the same device or computer at the same time without interfering, -* release versions do not appear on the beta download pages, and -* easier analysis of crash reports and user feedback. - -We propose the following method to set version numbers in your beta versions: - -* Use both `Bundle Version` and `Bundle Version String, short` in your Info.plist. -* "Bundle Version" should contain a sequential build number, e.g. 1, 2, 3. -* "Bundle Version String, short" should contain the target official version number, e.g. 1.0. - -## HowTo - -The recommended way to do versioning of your app versions is as follows: - -- Each version gets an ongoing `build` number which increases by `1` for every version as `CFBundleVersion` in `Info.plist` -- Additionally `CFBundleShortVersionString` in `Info.plist` will contain you target public version number as a string like `1.0.0` - -This ensures that each app version is uniquely identifiable, and that live and beta version numbers never ever collide. - -This is how to set it up with Xcode 4: - -1. Pick `File | New`, choose `Other` and `Configuration Settings File`, this gets you a new .xcconfig file. -2. Name it `buildnumber.xcconfig` -3. Add one line with this content: `BUILD_NUMBER = 1` -4. Then click on the project on the upper left in the file browser (the same place where you get to build settings), click on the project again in the second-to-left panel, and click on the Info tab at the top of the inner panel. -5. There, you can choose `Based on Configuration File` for each of your targets for each of your configurations (debug, release, etc.) -6. Select your target -7. Select the `Summary` tab -8. For `Build` enter the value: `${BUILD_NUMBER}` -9. Select the `Build Phases` tab -10. Select `Add Build Phase` -11. Choose `Add Run Script` -12. Add the following content: - - if [ "$CONFIGURATION" == "AdHoc_Distribution" ] - then /usr/bin/perl -pe 's/(BUILD_NUMBER = )(\d+)/$1.($2+1)/eg' -i buildnumber.xcconfig - fi -13. Change `AdHoc_Distribution` to the actual name of the Xcode configuration(s) you wnat the build number to be increased. - - *Note:* Configuration names should not contain spaces! -14. If you want to increase the build number before the build actuallry starts, just drag it up \ No newline at end of file diff --git a/submodules/HockeySDK-iOS/Documentation/Guides/Changelog.md b/submodules/HockeySDK-iOS/Documentation/Guides/Changelog.md deleted file mode 100644 index 64aa7af548..0000000000 --- a/submodules/HockeySDK-iOS/Documentation/Guides/Changelog.md +++ /dev/null @@ -1,1072 +0,0 @@ -## 5.1.2 - -- [IMPROVEMENT] This release uses Xcode 9.2 to compile the SDK. [#502](https://github.com/bitstadium/HockeySDK-iOS/pull/503) -- [BUGFIX] Fix warnings when integrating the SDK as source in Xcode 9. [#501](https://github.com/bitstadium/HockeySDK-iOS/pull/501) -- [BUGFIX] Fix a potential memory leak in `BITChannel`. [#500](https://github.com/bitstadium/HockeySDK-iOS/pull/500) -- [BUGFIX] Version 5.1.X broke support for app extension. We're sorry about this and we've updated our test matrix to make sure this does not happen again. [#499](https://github.com/bitstadium/HockeySDK-iOS/pull/499) -- [BUGFIX] Fix a bug in the Feedback UI when Feedback was shown in landscape. [#498](https://github.com/bitstadium/HockeySDK-iOS/pull/498) - -## 5.1.1 - -- [BUGFIX] Fixes a critical bug that would cause apps to freeze when calling `trackEvent` in UIApplicationDelegate callbacks. [#492](https://github.com/bitstadium/HockeySDK-iOS/pull/493) -- [BUGFIX] Fix a critical bug in the crashonly variant of the SDK. [#49](https://github.com/bitstadium/HockeySDK-iOS/pull/494) - -## 5.1.0 - -- [FEATURE] Add Turkish localization thanks to [Ozgur](https://github.com/ozgur).[#478](https://github.com/bitstadium/HockeySDK-iOS/pull/478) -- [FEATURE] Add support to detect low memory and OS kill heuristics for extensions. Thx to [Dave Weston](https://github.com/dtweston) for this! [#470](https://github.com/bitstadium/HockeySDK-iOS/pull/470) -- [IMPROVEMENT] Support tracking events in the background. [#475](https://github.com/bitstadium/HockeySDK-iOS/pull/475) -- [FIX] Improvements around thread-safety and concurrency for Metrics. [#471](https://github.com/bitstadium/HockeySDK-iOS/pull/471) [#479](https://github.com/bitstadium/HockeySDK-iOS/pull/479) -- [FIX] Fix runtime warnings of Xcode 9's main thread checker tool. [#484](https://github.com/bitstadium/HockeySDK-iOS/pull/484) -- [FIX] Fix caching of previews for attachments to Feedback. [#487](https://github.com/bitstadium/HockeySDK-iOS/pull/487) - -## 5.0.0 - -- [IMPROVEMENT] Use `UIAlertController` in Feedback instead of `UIAlertView`. [#460](https://github.com/bitstadium/HockeySDK-iOS/pull/460) -- [BUGFIX] Fix bugs in the Feedback UI on iOS 11. [#459](https://github.com/bitstadium/HockeySDK-iOS/pull/459) - -## 5.0.0-beta.2 - -- [FEATURE] Added support for Metrics in app extensions. [#449](https://github.com/bitstadium/HockeySDK-iOS/pull/449) -- [FEATURE] User Metrics can now be enabled after it was disabled [#451)(https://github.com/bitstadium/HockeySDK-iOS/pull/451) -- [IMPROVEMENT] Don't use `UIAlertView` but `UIAlertController`.[#446](https://github.com/bitstadium/HockeySDK-iOS/pull/446) -- [IMPROVEMENT] `BITAttributedLabel` is now based on `TTTAttributedLabel` 2.0. [#450](https://github.com/bitstadium/HockeySDK-iOS/pull/450) -- [BUGFIX] Fix a bug in `BITAuthenticator`. [#447](https://github.com/bitstadium/HockeySDK-iOS/pull/447) -- [BUGFIX] Fix for a bug in `BITImageAnnotation`. [#453](https://github.com/bitstadium/HockeySDK-iOS/pull/453) - -## 5.0.0-beta.1 - -This version drops support for iOS 7. There is not other breaking change at this point. - -- [IMPROVEMENT] The code has been cleaned up as we have decided to drop support for iOS 7. -- [IMPROVEMENT] `properties` of type `NSString` now use the `copy` attribute. -- [BUGFIX] The logic that makes sure that the for HockeySDK-iOS is excluded from backups was changed to make sure it doesn't block app launch [#443](https://github.com/bitstadium/HockeySDK-iOS/pull/443). - -## 4.1.6 - -- [BUGFIX] Fixed a string in the Italian translation [#430](https://github.com/bitstadium/HockeySDK-iOS/pull/430). -- [BUGFIX] Fix bug that prevented images that were attached to Feedback from loading [#428](https://github.com/bitstadium/HockeySDK-iOS/pull/428) – thx to [Zoltan](https://github.com/Xperion-meszaroz) for the contribution. -- [IMPROVEMENT] Improved the accessibility of the Feedback UI, it now uses the proper accessibility traits. -- [IMPROVEMENT] The crash-only flavor of the SDK now doesn't contain the Metrics feature. -- [IMPROVEMENT] The SDK can be compiled using Xcode 9 [#423](https://github.com/bitstadium/HockeySDK-iOS/pull/423) – thx to [Stephan](https://github.com/diederich) and [Piet](https://github.com/pietbrauer) for the contribution! -- [IMPROVEMENT] Metrics info will be send to the backend every time the application goes from foreground to background [#429](https://github.com/bitstadium/HockeySDK-iOS/pull/429) - thx to [Ivan](https://github.com/MatkovIvan) for the contribution. -- [IMPROVEMENT] Our README.md now use gender-neutral language [#427](https://github.com/bitstadium/HockeySDK-iOS/pull/427). - -## 4.1.5 - -This release officially drops the support for iOS 6. - -- [BUGFIX] Remove the dependency for AssetLibrary for the default subspec in our podspec [#403](https://github.com/bitstadium/HockeySDK-iOS/pull/403). Thanks to [@tschob](https://github.com/tschob) for the pointer. -- [BUGFIX] A couple of visual bugs have been fixed [#404](https://github.com/bitstadium/HockeySDK-iOS/pull/404) thanks to [@dweston](https://github.com/dtweston). -- [IMPROVEMENT] We have improved accessibility of our feedback UI [#409](https://github.com/bitstadium/HockeySDK-iOS/pull/409) with help by [@erychagov](https://github.com/erychagov). - -## 4.1.4 - -- [IMPROVEMENT] Test targets won't be build in the run phase of the framework, which makes it possible to build individual configurations when using Carthage. Thanks a lot @wiedem for your contribution! [394](https://github.com/bitstadium/HockeySDK-iOS/pull/394) -- [IMPROVEMENT] We've reverted to a build based on PLCrashReporter 1.2.1 as 1.3 comes with unintended namespace collisions in some edge cases that result in worse crash reporting than you were used to. -- [BUGFIX] Fixes a crash on iOS 9 when attaching data to feedback [#395](https://github.com/bitstadium/HockeySDK-iOS/issues/395) -- [BUGFIX] Disabling the `BITFeedbackManager` now disables the various `BITFeedbackObservationModes`. [#390](https://github.com/bitstadium/HockeySDK-iOS/pull/390) - -## 4.1.3 - -- [NEW] Added `forceNewFeedbackThreadForFeedbackManager:`-callback to `BITFeedbackManagerDelegate` to force a new feedback thread for each new feedback. -- [NEW] Norwegian (Bokmal) localization -- [NEW] Persian (Farsi) localization -- [BUGFIX] Fix analyzer warning in `BITChannelManagerTests` -- [BUGFIX] Add check for nil in `BITChannel`. - -## 4.1.2 - -- [NEW] New `shouldDisplayUpdateAlertForUpdateManager`-API [#339](https://github.com/bitstadium/HockeySDK-iOS/pull/339) to make the moment of appearance for custom update UI even more customizable. -- [IMPROVEMENT] Fix static analyzer warnings. [#351](https://github.com/bitstadium/HockeySDK-iOS/pull/351) -- [IMPROVEMENT] Internal structure of embedded frameworks changed [#352](https://github.com/bitstadium/HockeySDK-iOS/pull/352) -- [IMPROVEMENT] Upgrade to PLCrashReporter 1.3 -- [BUGFIX] Enable bitcode in all configurations [#344](https://github.com/bitstadium/HockeySDK-iOS/pull/344) -- [BUGFIX] Fixed anonymisation of binary paths when running in the simulator [#347](https://github.com/bitstadium/HockeySDK-iOS/pull/347) -- - [BUGFIX] Rename configurations to not break Carthage integration [#353](https://github.com/bitstadium/HockeySDK-iOS/pull/353) - -## 4.1.1 - -**Attention** Due to changes in iOS 10, it is now necessary to include the `NSPhotoLibraryUsageDescription` in your app's Info.plist file if you want to use HockeySDK's Feedback feature. Since using the feature without the plist key present could lead to an App Store rejection, our default CocoaPods configuration does not include the Feedback feature anymore. -If you want to continue to use it, use this in your `Podfile`: - -```ruby -pod "HockeySDK", :subspecs => ['AllFeaturesLib'] -``` - -Additionally, we now also provide a new flavor in our binary distribution. To use all features, including Feedback, use `HockeySDK.embeddedframework` from the `HockeySDKAllFeatures` folder. - -- [NEW] The property `userDescription` on `BITCrashMetaData` had to be renamed to `userProvidedeDescription` to provide a name clash with Apple Private API -- [IMPROVEMENT] Warn if the Feedback feature is being used without `NSPhotoLibraryUsageDescription` being present -- [IMPROVEMENT] Updated Chinese translations -- [IMPROVEMENT] Set web view baseURL to `about:blank` to improve security -- [BUGFIX] Fix an issue in the telemetry channel that could be triggered in multi-threaded environments -- [BUGFIX] Fix several small layout issues by updating to a newer version of TTTAttributedLabel -- [BUGFIX] Fix app icons with unusual filenames not showing in the in-app update prompt - -## 4.1.0 - -- Includes improvements from 4.0.2 release of the SDK. -- [NEW] Additional API to track an event with properties and measurements. - -## 4.1.0-beta.2 - -- [BUGFIX] Fixes an issue where the whole app's Application Support directory was accidentally excluded from backups. -This SDK release explicitly includes the Application Support directory into backups. If you want to opt-out of this fix and keep the Application Directory's backup flag untouched, add the following line above the SDK setup code: - - - Objective-C: - ```objc - [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"BITExcludeApplicationSupportFromBackup"]; - ``` - - - Swift: - ```swift - NSUserDefaults.standardUserDefaults().setBool(true, forKey: "BITExcludeApplicationSupportFromBackup") - ``` - -- [NEW] Add more fine-grained log levels -- [NEW] Add ability to connect existing logging framework -- [BUGFIX] Make CrashManager property `serverURL` individual setable -- [BUGFIX] Properly dispatch `dismissViewController` call to main queue -- [BUGFIX] Fixes an issue that prevented preparedItemsForFeedbackManager: delegate method from working - -## Version 4.1.0-beta.1 - -- [IMPROVEMENT] Prevent User Metrics from being sent if `BITMetricsManager` has been disabled. - -## Version 4.1.0-alpha.2 - -- [BUGFIX] Fix different bugs in the events sending pipeline - -## Version 4.1.0-alpha.1 - -- [NEW] Add ability to track custom events -- [IMPROVEMENT] Events are always persisted, even if the app crashes -- [IMPROVEMENT] Allow disabling `BITMetricsManager` at any time -- [BUGFIX] Server URL is now properly customizable -- [BUGFIX] Fix memory leak in networking code -- [IMPROVEMENT] Optimize tests and always build test target -- [IMPROVEMENT] Reuse `NSURLSession` object -- [IMPROVEMENT] Under the hood improvements and cleanup - -## Version 4.0.2 - -- [BUGFIX] Add Bitcode marker back to simulator slices. This is necessary because otherwise `lipo` apparently strips the Bitcode sections from the merged library completely. As a side effect, this unfortunately breaks compatibility with Xcode 6. [#310](https://github.com/bitstadium/HockeySDK-iOS/pull/310) -- [IMPROVEMENT] Improve error detection and logging during crash processing in case the app is sent to the background while crash processing hasn't finished.[#311](https://github.com/bitstadium/HockeySDK-iOS/pull/311) - -## Version 4.0.1 - -- [BUGFIX] Fixes an issue where the whole app's Application Support directory was accidentally excluded from backups. -This SDK release explicitly includes the Application Support directory into backups. If you want to opt-out of this fix and keep the Application Directory's backup flag untouched, add the following line above the SDK setup code: - - - Objective-C: - ```objc - [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"kBITExcludeApplicationSupportFromBackup"]; - ``` - - - Swift: - ```swift - NSUserDefaults.standardUserDefaults().setBool(true, forKey: "kBITExcludeApplicationSupportFromBackup") - ``` - -- [BUGFIX] Fixes an issue that prevented preparedItemsForFeedbackManager: delegate method from working - -## Version 4.0.0 - -- [NEW] Added official Carthage support -- [NEW] Added `preparedItemsForFeedbackManager:` method in `BITFeedbackManagerDelegate` to allow to provide items with every possible method of showing the feedback compose dialog. -- [UPDATE] Our CrashOnly binary now includes User Metrics which enables crash free users statistics -- [UPDATE] Deprecate `feedbackComposerPreparedItems` property in favor of the new delegate method. -- [IMPROVEMENT] Prefix GZIP category on NSData to prevent symbol collisions -- [BUGFIX] Add minor UI bug when adding arrow annotation to feedback image - -## Version 4.0.0-beta.1 - -- [NEW] User Metrics including users and sessions data is now in public beta - -## Version 4.0.0-alpha.2 - -- [UPDATE] Include changes from HockeySDK 3.8.6 - -## Version 4.0.0-alpha.1 - -- [NEW] Added `BITMetricsManager` to track users and sessions -- [UPDATE] Remove previously deprecated UpdateManagerDelegate method `-viewControllerForUpdateManager:` -- [UPDATE] Remove previously deprecated CrashManagerDelegate methods `-userNameForCrashManager:` and `-userEmailForCrashManager:` -- [UPDATE] Remove previously deprecated property `appStoreEnvironment` -- [UPDATE] Remove previously deprecated misspelled `timeintervalCrashInLastSessionOccured` property -- [UPDATE] Remove previously deprecated misspelled `BITFeedbackListViewCellPresentatationStyle` enum - -## Version 3.8.6 - -- [UPDATE] Some minor refactoring -- [BUGFIX] Fix crash in `BITCrashReportTextFormatter` in cases where processPath is unexpectedly nil -- [BUGFIX] Fix bug where feedback image could only be added once -- [BUGFIX] Fix URL encoding bug in BITUpdateManager -- [BUGFIX] Include username, email, etc. in `appNotTerminatingCleanly` reports -- [BUGFIX] Fix NSURLSession memory leak in Swift apps -- [BUGFIX] Fix issue preventing attachment from being included when sending non-clean termination report -- [IMPROVEMENT] Anonymize binary path in crash report -- [IMPROVEMENT] Support escaping of additional characters (URL encoding) -- [IMPROVEMENT] Support Bundle Identifiers which contain whitespaces - -## Version 3.8.5 - -- [UPDATE] Some minor improvements to our documentation -- [BUGFIX] Fix a crash where `appStoreReceiptURL` was accidentally accessed on iOS 6 -- [BUGFIX] Fix a warning when implementing `BITHockeyManagerDelegate` - -## Version 3.8.4 - -- [BUGFIX] Fix a missing header in the `HockeySDK.h` umbrella -- [BUGFIX] Fix several type comparison warnings - -## Version 3.8.3 - -- [NEW] Adds new `appEnvironment` property to indicate the environment the app is running in. This replaces the old `isAppStoreEnvironment` which is now deprecated. We can now differentiate between apps installed via TestFlight or the AppStore -- [NEW] Distributed zip file now also contains our documentation -- [UPDATE] Prevent issues with duplicate symbols from PLCrashReporter -- [UPDATE] Remove several typos in our documentation and improve instructions for use in extensions -- [UPDATE] Add additional nil-checks before calling blocks -- [UPDATE] Minor code readability improvements -- [BUGFIX] `BITFeedbackManager`: Fix Feedback Annotations not working on iPhones running iOS 9 -- [BUGFIX] Switch back to using UIAlertView to prevent several issues. We will add a more robust solution which uses UIAlertController in a future update. -- [BUGFIX] Fix several small issues in our CrashOnly builds -- [BUGFIX] Minor fixes for memory leaks -- [BUGFIX] Fix crashes because completion blocks were not properly dispatched on the main thread - -## Version 3.8.2 - -- [UPDATE] Added support for Xcode 6.x -- [UPDATE] Requires iOS 7 or later as base SDK, deployment target iOS 6 or later -- [UPDATE] Updated PLCrashReporter build to exclude Bitcode in Simulator slices - -## Version 3.8.1 - -- [UPDATE] Updated PLCrashReporter build using Xcode 7 (7A220) - -## Version 3.8 - -- [NEW] Added Bitcode support -- [UPDATE] Requires Xcode 7 or later -- [UPDATE] Requires iOS 9 or later as base SDK, deployment target iOS 6 or later -- [UPDATE] Updated PLCrashReporter build using Xcode 7 -- [UPDATE] Use `UIAlertController` when available -- [UPDATE] Added full support for `NSURLSession` -- [UPDATE] Removed statusbar adjustment code (which isn't needed any longer) -- [UPDATE] Removed kBITTextLabel... defines and use NSText.. instead -- [UPDATE] Removed a few `#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1` since iOS 7 or later is now required as base SDK -- [BUGFIX] `BITFeedbackManager`: Fixed feedback compose view rotation issue -- [BUGFIX] `BITFeedbackManager`: Fixed `Add Image` button not always presented centered -- [BUGFIX] Additional minor fixes - -## Version 3.8-RC.1 - -- [UPDATE] Added full support for `NSURLSession` -- [BUGFIX] `BITFeedbackManager`: Fixed feedback compose view rotation issue -- [BUGFIX] `BITFeedbackManager`: Fixed `Add Image` button not always presented centered -- [BUGFIX] Additional minor fixes - -## Version 3.8-Beta.1 - -- [NEW] Added Bitcode support -- [UPDATE] Requires Xcode 7 or later -- [UPDATE] Requires iOS 7 or later as base SDK -- [UPDATE] Silenced deprecation warnings for `NSURLConnection` calls, these will be refactored in a future update -- [UPDATE] Removed statusbar adjustment code (which isn't needed any longer) -- [UPDATE] Removed kBITTextLabel... defines and use NSText.. instead -- [UPDATE] Removed a few `#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1` since iOS 7 or later is now required as base SDK -- [UPDATE] Use `UIAlertController` when available - -## Version 3.7.3 - -- [BUGFIX] `BITCrashManager`: Updated PLCrashReporter build created with Xcode 6.4 to solve a duplicate symbol error some users are experiencing -- [BUGFIX] `BITUpdateManager`: Fixed updating an app not triggering a crash report if `enableAppNotTerminatingCleanlyDetection` is enabled -- [BUGFIX] Additional minor fixes - -## Version 3.7.2 - -- [BUGFIX] `BITCrashManager`: Added workaround for a bug observed in iOS 9 beta's dyld triggering an infinite loop on startup -- [BUGFIX] `BITFeedbackManager`: Fixed a crash in the feedback UI that can occur when rotating the device while data is being loaded -- [BUGFIX] Fixed `Info.plist` entries in `HockeySDKResources.bundle` which cause Xcode 7 to show an error when uploading an app to iTunes Connect -- [BUGFIX] Additional minor fixes - -## Version 3.7.1 - -- [BUGFIX] `CocoaPods`: Fixes the default podspec with binary distribution -- [BUGFIX] `CocoaPods`: Changes `HockeySDK-Source` to use non configurable approach, since we couldn't make it work reliably in all scenarios - -## Version 3.7.0 - -- [NEW] Simplified installation process. If support for modules is enabled in the target project (default for most projects), it’s no longer necessary to add the frameworks manually -- [NEW] `CocoaPods`: Default pod uses binary distribution and offers crash only build as a subspec -- [NEW] `CocoaPods`: New `HockeySDK-Source` pod integrates via source code and offers feature set customization via subspecs. Note: We do not support building with Xcode 7 yet! -- [NEW] `BITCrashManager`: Added support for unhandled C++ exceptions (requires to link `libc++`) -- [NEW] `BITCrashManager`: Sending crash reports via `NSURLSession` whenever possible -- [NEW] `BITCrashManager`: Added process ID to `BITCrashDetails` -- [NEW] `BITCrashManager`: Added `CFBundleShortVersionString` value to crash reports -- [NEW] `BITFeedbackManager`: "Add Image" button in feedback compose view can now be hidden using `feedbackComposeHideImageAttachmentButton` property -- [NEW] `BITFeedbackManagerDelegate`: Added `allowAutomaticFetchingForNewFeedbackForManager:` to define if the SDK should fetch new messages on app startup and when the app is coming into foreground. -- [NEW] Added disableInstallTracking property to disable installation tracking (AppStore only). -- [UPDATE] Restructured installation documentation -- [BUGFIX] `BITCrashManager`: Fixed offline issue showing crash alert over and over again with unsent crash reports -- [BUGFIX] `BITFeedbackManager`: Improved screenshot handling on slow devices -- [BUGFIX] `BITStoreUpdateManager`: Delegate property wasn't propagated correctly -- [BUGFIX] Fixed various compiler warnings & other improvements - -## Version 3.6.4 - -- [BUGFIX] Fixed a build issue - -## Version 3.6.3 - -- [NEW] `BITCrashManager`: Added launch time to crash reports -- [NEW] `BITFeedbackManager`: Added support for setting tintColor for feedback list buttons -- [NEW] `BITFeedbackManager`: Added `feedbackComposerPreparedItems` to pre-fill feedback compose UI message with given items -- [NEW] `BITUpdateManagerDelegate`: Added `willStartDownloadAndUpdate` to be notified before beta update starts -- [UPDATE] Improved CocoaPods support to allow building as a native iOS 8 framework -- [UPDATE] Keychain is now accessed with `kSecAttrAccessibleAlwaysThisDeviceOnly` to support apps that are running in the background and the device is still locked -- [UPDATE] Reduced file size of images in `HockeySDKResources.bundle` by 63% -- [UPDATE] `BITCrashManager`: `timeintervalCrashInLastSessionOccured` property is deprecated due to typo, use `timeIntervalCrashInLastSessionOccurred` instead -- [UPDATE] `BITFeedbackManager`: `BITFeedbackListViewCellPresentatationStyle` is deprecated due to a typo, use `BITFeedbackListViewCellPresentationStyle` instead -- [UPDATE] `BITAuthenticator`: Use NSLog instead of an UIAlertView in case of keychain issues -- [BUGFIX] `BITCrashManager`: Fixed issue with `appNotTerminatingCleanlyDetection` for some scenarios -- [BUGFIX] `BITFeedbackManager`: Fixed a crash when deleting feedback attachments -- [BUGFIX] `BITFeedbackManager`: Fixed a crash related to viewing attachments -- [BUGFIX] `BITFeedbackManager`: Fixed landscape screenshot issues in iOS 8 -- [BUGFIX] `BITFeedbackManager`: Fixed various issues in feedback compose UI -- [BUGFIX] `BITFeedbackManager`: Fixed loading issues for attachments in feedback UI -- [BUGFIX] `BITFeedbackManager`: Fixed statusbar issues and the image attachment picker with apps not showing a status bar -- [BUGFIX] Removed a header file from the crash only build that is not needed -- [BUGFIX] Fixed various typos in documentation, properties -- [BUGFIX] Fixed various compiler warnings -- [BUGFIX] Various additional fixes - -## Version 3.6.2 - -- [UPDATE] Store anonymous UUID asynchronously into the keychain to work around rare keychain blocking behavior -- [UPDATE] `BITCrashManager`: Improved detecting app specific binary images in crash report for improved crash grouping on the server -- [UPDATE] `BITUpdateManager`: Added new `updateManagerWillExitApp` delegate method -- [UPDATE] `BITUpdateManager`: Don't save any file when app was installed from App Store -- [BUGFIX] `BITCrashManager`: Fixed issues with sending crash reports for apps with xml tags in the app name -- [BUGFIX] `BITFeedbackManager`: Fixed screenshot trigger issue not always fetching the last taken image -- [BUGFIX] `BITFeedbackManager`: Fixed compose view issue with predefined text -- [BUGFIX] Fixed a warning when integrating the binary framework for only crash reporting -- [BUGFIX] Fixed compiler warnings -- [BUGFIX] Various additional fixes - -## Version 3.6.1 - -- [BUGFIX] Fixed feedback compose view to correctly show the text in landscape on iOS 8 - -## Version 3.6 - -- [NEW] `BITCrashManager`: Added support for iOS 8 Extensions -- [NEW] `BITCrashManager`: Option to add a custom UI flow before sending a crash report, e.g. to ask users for more details (see `setAlertViewHandler:`) -- [NEW] `BITCrashManager`: Provide details on a crash report (see `lastSessionCrashDetails` and `BITCrashDetails`) -- [NEW] `BITCrashManager`: Experimental support for detecting app kills triggered by iOS while the app is in foreground (see `enableAppNotTerminatingCleanlyDetection`) -- [NEW] `BITCrashManager`: Added `didReceiveMemoryWarningInLastSession` which indicates if the last app session did get a memory warning by iOS -- [NEW] `BITFeedbackManager`: Attach and annotate images and screenshots -- [NEW] `BITFeedbackManager`: Attach any binary data to compose message view (see `showFeedbackComposeViewWithPreparedItems:`) -- [NEW] `BITFeedbackManager`: Show a compose message with a screenshot image attached using predefined triggers (see `feedbackObservationMode`) or your own custom triggers (see `showFeedbackComposeViewWithGeneratedScreenshot`) -- [NEW] Minimum iOS Deployment version is now iOS 6.0 -- [NEW] Requires to link additional frameworks: `AssetLibrary`, `MobileCoreServices`, `QuickLook` -- [UPDATE] `BITCrashManager`: Updated `setCrashCallbacks` handling now using `BITCrashManagerCallbacks` instead of `PLCrashReporterCallbacks` (which is no longer public) -- [UPDATE] `BITCrashManager`: Crash reports are now sent individually if there are multiple pending -- [UPDATE] `BITUpdateManager`: Improved algorithm for fetching an optimal sized app icon for the Update View -- [UPDATE] `BITUpdateManager`: Properly consider paragraphs in release notes when presenting them in the Update view -- [UPDATE] Property `delegate` in all components is now private. Set the delegate on `BITHockeyManager` only! -- [UPDATE] Removed support for Atlassian JMC -- [BUGFIX] Various additional fixes -

- -## Version 3.6.0 Beta 2 - -- [NEW] `BITFeedbackManager`: Screenshot feature is now part of the public API -- [UPDATE] `BITFeedbackManager`: Various improvements for the screenshot feature -- [UPDATE] `BITFeedbackManager`: Added `BITHockeyAttachment` for more customizable attachments to feedback (`content-type`, `filename`) -- [UPDATE] `BITUpdateManager`: Improved algorithm for fetching an optimal sized app icon for the Update View -- [UPDATE] `BITUpdateManager`: Properly consider paragraphs in releases notes when presenting them in the Update View -- [UPDATE] `BITCrashManager`: Updated PLCrashReporter to version 1.2 -- [UPDATE] `BITCrashManager`: Added `osVersion` and `osBuild` properties to `BITCrashDetails` -- [BUGFIX] `BITCrashManager`: Use correct filename for crash report attachments -- [UPDATE] Property `delegate` in all components is now private. Set the delegate on `BITHockeyManager` only! -- [BUGFIX] Various additional fixes -

- -## Version 3.6.0 Beta 1 - -- [NEW] Minimum iOS Deployment version is now iOS 6.0 -- [NEW] Requires to link additional frameworks: `AssetLibrary`, `MobileCoreServices`, `QuickLook` -- [NEW] `BITFeedbackManager`: Attach and annotate images and screenshots -- [NEW] `BITFeedbackManager`: Attach any binary data to compose message view (see `showFeedbackComposeViewWithPreparedItems:`) -- [NEW] `BITFeedbackManager`: Show a compose message with a screenshot image attached using predefined triggers (see `feedbackObservationMode`) or your own custom triggers (see `showFeedbackComposeViewWithGeneratedScreenshot`) -- [NEW] `BITCrashManager`: Option to add a custom UI flow before sending a crash report, e.g. to ask users for more details (see `setAlertViewHandler:`) -- [NEW] `BITCrashManager`: Provide details on a crash report (see `lastSessionCrashDetails`) -- [NEW] `BITCrashManager`: Experimental support for detecting app kills triggered by iOS while the app is in foreground (see `enableAppNotTerminatingCleanlyDetection`) -- [NEW] `BITCrashManager`: Added `didReceiveMemoryWarningInLastSession` which indicates if the last app session did get a memory warning by iOS -- [UPDATE] `BITCrashManager`: Updated `setCrashCallbacks` handling now using `BITCrashManagerCallbacks` instead of `PLCrashReporterCallbacks` (which is no longer public) -- [UPDATE] `BITCrashManager`: Crash reports are now send individually if there are multiple pending -- [UPDATE] Removed support for Atlassian JMC -- [BUGFIX] Fixed an incorrect submission warning about referencing non-public selector `attachmentData` -

- -## Version 3.5.7 - -- [UPDATE] Easy Swift integration for binary distribution (No Objective-C bridging header required) -- [UPDATE] `BITAuthenticator`: Improved keychain handling -- [UPDATE] `BITUpdateManager`: Improved iOS 8 In-App-Update process handling -- [BUGFIX] `BITUpdateManager`: Fixed layout issue for resizable iOS layout -- [BUGFIX] Fixed an iTunes Connect warning for `attachmentData` property -

- -## Version 3.5.6 - -- [UPDATE] `BITCrashManager`: Updated PLCrashReporter to version 1.2 -- [UPDATE] `BITUpdateManager`: Improved algorithm to find the optimal app icon -- [BUGFIX] `BITAuthenticator`: Fixed problem with authorization and iOS 8 -- [BUGFIX] Fixed a problem with integration test and iOS 8 -

- -## Version 3.5.5 - -- [NEW] `BITCrashManager`: Added support for adding a binary attachment to crash reports -- [NEW] `BITCrashManager`: Integrated PLCrashReporter 1.2 RC5 (with 2 more fixes) -- [BUGFIX] `BITUpdateManager`: Fixed problem with `checkForUpdate` when `updateSetting` is set to `BITUpdateCheckManually` -- [BUGFIX] `BITAuthenticator`: Fixed keychain warning alert showing app on launch if keychain is locked -- [BUGFIX] `BITAuthenticator`: Fixed a possible assertion problem with auto-authentication (when using custom SDK builds without assertions being disabled) -- [BUGFIX] `BITAuthenticator`: Added user email to crash report for beta builds if BITAuthenticator is set to BITAuthenticatorIdentificationTypeWebAuth -- [BUGFIX] Fixed more analyzer warnings -

- -## Version 3.5.4 - -- [BUGFIX] Fix a possible crash before sending the crash report when the selector could not be found -- [BUGFIX] Fix a memory leak in keychain handling -

- -## Version 3.5.3 - -- [NEW] Crash Reports now provide the selector name e.g. for crashes in `objc_MsgSend` -- [NEW] Add setter for global `userID`, `userName`, `userEmail`. Can be used instead of the delegates. -- [UPDATE] On device symbolication is now optional, disabled by default -- [BUGFIX] Fix for automatic authentication not always working correctly -- [BUGFIX] `BITFeedbackComposeViewControllerDelegate` now also works for compose view controller used by the feedback list view -- [BUGFIX] Fix typos in documentation -

- -## Version 3.5.2 - -- [UPDATE] Make sure a log message appears in the console if the SDK is not setup on the main thread -- [BUGFIX] Fix usage time always being send as `0` instead of sending the actual usage time -- [BUGFIX] Fix "Install" button in the mandatory update alert not working and forcing users to use the "show" button and then install from the update view instead -- [BUGFIX] Fix possible unused function warnings -- [BUGFIX] Fix two warnings when `-Wshorten-64-to-32` is set. -- [BUGFIX] Fix typos in documentation -

- -## Version 3.5.1 - -- General - - - [NEW] Add new initialize to make the configuration easier: `[BITHockeyManager configureWithIdentifier:]` - - [NEW] Add `[BITHockeyManager testIdentifier]` to check if the SDK reaches the server. The result is shown on the HockeyApp website on success. - - [UPDATE] `delegate` can now also be defined using the property directly (instead of using the configureWith methods) - - [UPDATE] Use system provided Base64 encoding implementation - - [UPDATE] Improved logic to choose the right `UIWindow` instance for dialogs - - [BUGFIX] Fix compile issues when excluding all modules but crash reporting - - [BUGFIX] Fix warning on implicit conversion from `CGImageAlphaInfo` to `CGBitmapInfo` - - [BUGFIX] Fix warnings for implicit conversions of `UITextAlignment` and `UILineBreakMode` - - [BUGFIX] Various additional smaller bug fixes -

- -- Crash Reporting - - - [NEW] Integrated PLCrashReporter 1.2 RC 2 - - [NEW] Add `generateTestCrash` method to more quickly test the crash reporting (automatically disabled in App Store environment!) - - [NEW] Add PLCR header files to the public headers in the framework - - [NEW] Add the option to define callbacks that will be executed prior to program termination after a crash has occurred. Callback code has to be async-safe! - - [UPDATE] Change the default of `showAlwaysButton` property to `YES` - - [BUGFIX] Always format date and timestamps in crash report in `en_US_POSIX` locale. -

- -- Feedback - - - [UPDATE] Use only one activity view controller per UIActivity - - [BUGFIX] Fix delete button appearance in feedback list view on iOS 7 when swiping a feedback message - - [BUGFIX] Comply to -[UIActivity activityDidFinish:] requirements - - [BUGFIX] Use non-deprecated delegate method for `BITFeedbackActivity` -

- -- Ad-Hoc/Enterprise Authentication - - - [NEW] Automatic authorization when app was installed over the air. This still requires to call `[BITAuthenticator authenticateInstallation];` after calling `startManager`! - - [UPDATE] Set the tintColor in the auth view and modal views navigation controller on iOS 7 - - [UPDATE] Show an alert if the authentication token could not be stored into the keychain - - [UPDATE] Use UTF8 encoding for auth data - - [UPDATE] Replace email placeholder texts - - [BUGFIX] Make sure the authentication window is always correctly dismissed - - [BUGFIX] Fixed memory issues -

- -- Ad-Hoc/Enterprise Updates - - - [NEW] Provide alert option to show mandatory update details - - [NEW] Add button to expired page (and alert) that lets the user check for a new version (can be disabled using `disableUpdateCheckOptionWhenExpired`) - - [UPDATE] Usage metrics are now stored in an independent file instead of using `NSUserDefaults` -

- - -## Version 3.5.0 - -- General - - - [NEW] Added support for iOS 7 - - [NEW] Added support for arm64 architecture - - [NEW] Added `BITStoreUpdateManager` for alerting the user of available App Store updates (disabled by default) - - [NEW] Added `BITAuthenticator` class for authorizing installations (Ad-Hoc/Enterprise builds only!) - - [NEW] Added support for apps starting in the background - - [NEW] Added possibility to build custom frameworks including/excluding specific modules from the static library (see `HockeySDKFeatureConfig.h`) - - [NEW] Added public access to the anonymous UUID that the SDK generates per app installation - - [NEW] Added possibility to overwrite SDK specific localization strings in the apps localization files - - [UPDATE] Updated localizations provided by [Wordcrafts.de](http://wordcrafts.de): - Chinese, Dutch, English, French, German, Hungarian, Italian, Japanese, Portuguese, Brazilian-Portuguese, Romanian, Russian, Spanish - - [UPDATE] User related data is now stored in the keychain instead of property files - - [UPDATE] SDK documentation improvements - - [BUGFIX] Fixed multiple compiler warnings - - [BUGFIX] Various UI updates and fixes -

- -- Crash Reporting - - - [NEW] Integrated PLCrashReporter 1.2 beta 3 - - [NEW] Added optional support for Mach exceptions - - [NEW] Added support for arm64 - - [UPDATE] PLCrashReporter build with `BIT` namespace to avoid collisions - - [UPDATE] Crash reporting is automatically disabled when the app is invoked with the debugger! - - [UPDATE] Automatically add the users UDID or email to crash reports in Ad-Hoc/Enterprise builds if they are provided by BITAuthenticator -

- -- Feedback - - - [NEW] New protocol to inform about incoming feedback messages, see `BITFeedbackManagerDelegate` - - [UPDATE] Added method in `BITFeedbackComposeViewControllerDelegate` to let the app know if the user submitted a new message or cancelled it -

- -- App Store Updates - - - [NEW] Inform user when a new version is available in the App Store (optional, disabled by default) -

- - -- Ad-Hoc/Enterprise Authentication - - - [NEW] `BITAuthenticator` identifies app installations, automatically disabled in App Store environments - - [NEW] `BITAuthenticator` can identify the user through: - - The email address of their HockeyApp account - - Login with their HockeyApp account (does not work with Facebook accounts!) - - Installation of the HockeyApp web-clip to provide the UDID (requires the app to handle URL callbacks) - - Web based login with their HockeyApp account - - [NEW] `BITAuthenticator` can require the authorization: - - Never - - On first app version launch - - Whenever the app comes into foreground (requires the device to have a working internet connection) - - [NEW] Option to customize the authentication flow - - [NEW] Possibility to use an existing URL scheme -

- -- Ad-Hoc/Enterprise Updates - - - [UPDATE] Removed delegate for getting the UDID, please migrate to the new `BITAuthenticator` - - [NEW] In-app updates are now only offered if the device matches the minimum OS version requirement -

- ---- - -## Version 3.5.0 RC 3 - -- General - - - [NEW] Added public access to the anonymous UUID that the SDK generates per app installation - - [NEW] Added possibility to overwrite SDK specific localization strings in the apps localization files - - [UPDATE] Podspec updates - - [BUGFIX] Fixed memory leaks - - [BUGFIX] Various minor bugfixes -

- -- Crash Reporting - - - [UPDATE] Integrated PLCrashReporter 1.2 beta 3 - - [BUGFIX] Fixed crash if minimum OS version isn't provided - - [BUGFIX] Update private C function to use BIT namespace -

- -- Feedback - - - [BUGFIX] Fixed some layout issues in the user info screen -

- -- Ad-Hoc/Enterprise Updates - - - [BUGFIX] Fixed update view controller not showing updated content after using the check button - - [BUGFIX] Fixed usage value being reset on every app cold start -

- -- Ad-Hoc/Enterprise Authentication - - - [NEW] Added web based user authentication - - [UPDATE] IMPORTANT: You need to call `[[BITHockeyManager sharedHockeyManager].authenticator authenticateInstallation];` yourself after startup when the authentication and/or verification should be performed and when it is safe to present a modal view controller! - - [UPDATE] Removed `automaticMode`. You now need to call `authenticateInstallation` when it is safe to do so or handle the complete process yourself. -

- -## Version 3.5.0 RC 2 - -- General - - - [BUGFIX] Remove assertions from release build -

- -- Ad-Hoc/Enterprise Updates - - - [BUGFIX] Add new iOS 7 icon sizes detection and adjust corner radius -

- -## Version 3.5.0 RC 1 - -- General - - - [UPDATE] Documentation improvements nearly everywhere -

- -- Crash Reporting - - - [UPDATE] Integrated PLCrashReporter 1.2 beta 2 - - [UPDATE] 64 bit crash reports now contain the correct architecture string - - [UPDATE] Automatically add the users UDID or email to crash reports in Ad-Hoc/Enterprise builds if they are provided by BITAuthenticator - - [BUGFIX] Fixed userName, userEmail and userID not being added to crash reports -

- -- App Store Updates - - - [UPDATE] Changed default update check interval to weekly -

- -- Ad-Hoc/Enterprise Authentication - - - [NEW] Redesigned API for easier usage and more flexibility (please check the documentation!) - - [NEW] Added option to customize the authentication flow - - [NEW] Added option to provide a custom parentViewController for presenting the UI - - [NEW] Added possibility to use an existing URL scheme - - [BUGFIX] Fixed authentication UI appearing after updating apps without changing the authentication settings -

- -- Ad-Hoc/Enterprise Updates - - - [UPDATE] Don't add icon gloss to icons when running on iOS 7 - - [BUGFIX] Fixed a few iOS 7 related UI problems in the update view -

- - -## Version 3.5.0 Beta 3 - -- Feedback - - - [BUGFIX] Fix a layout issue with the compose feedback UI on the iPad with iOS 7 in landscape orientation -

- -- Ad-Hoc/Enterprise Authentication - - - [BUGFIX] Fix a possible crash in iOS 5 -

- - -## Version 3.5.0 Beta 2 - -- General - - - [NEW] Added support for apps starting in the background - - [UPDATE] Added updated CocoaSpec - - [BUGFIX] Various documentation improvements -

- -- Ad-Hoc/Enterprise Authentication - - - [BUGFIX] Fix duplicate count of installations -

- -- Ad-Hoc/Enterprise Updates - - - [BUGFIX] Update view not showing any versions - - [BUGFIX] Fix a crash presenting the update view on iOS 5 and iOS 6 -

- - -## Version 3.5.0 Beta 1 - -- General - - - [NEW] Added support for iOS 7 - - [NEW] Added experimental support for arm64 architecture - - [NEW] Added `BITStoreUpdateManager` for alerting the user of available App Store updates (disabled by default) - - [NEW] Added `BITAuthenticator` class for authorizing installations (Ad-Hoc/Enterprise builds only!) - - [NEW] Added possibility to build custom frameworks including/excluding specific modules from the static library (see `HockeySDKFeatureConfig.h`) - - [UPDATE] User related data is now stored in the keychain instead of property files - - [UPDATE] SDK documentation improvements - - [BUGFIX] Fixed multiple compiler warnings - - [BUGFIX] Fixed a few UI glitches, e.g. adjusting status bar style -

- -- Crash Reporting - - - [NEW] Integrated PLCrashReporter 1.2 beta 1 - - [NEW] Added optional support for Mach exceptions - - [NEW] Experimental support for arm64 (will be tested and improved once devices are available) - - [UPDATE] PLCrashReporter build with `BIT` namespace to avoid collisions - - [UPDATE] Crash reporting is automatically disabled when the app is invoked with the debugger! -

- -- Feedback - - - [NEW] New protocol to inform about incoming feedback messages, see `BITFeedbackManagerDelegate` - - [UPDATE] Added method in `BITFeedbackComposeViewControllerDelegate` to let the app know if the user submitted a new message or cancelled it -

- -- App Store Updates - - - [NEW] Inform user when a new version is available in the App Store (optional, disabled by default) -

- -- Ad-Hoc/Enterprise Updates and Authentication - - - [UPDATE] Removed delegate for getting the UDID, please migrate to the new `BITAuthenticator` - - [NEW] In-app updates are now only offered if the device matches the minimum OS version requirement - - [NEW] `BITAuthenticator` identifies app installations, automatically disabled in App Store environments - - [NEW] `BITAuthenticator` can identify the user through: - - The email address of his/her HockeyApp account - - Login with his/her HockeyApp account (does not work with Facebook accounts!) - - Installation of the HockeyApp web-clip to provide the UDID (requires the app to handle URL callbacks) - - [NEW] `BITAuthenticator` can require the authorization: - - Never - - Optionally, i.e. the user can skip the dialog - - On first app version launch - - Whenever the app comes into foreground (requires the device to have a working internet connection) -

- - -## Version 3.0.0 - -- General - - - [NEW] Added new Feedback module - - [NEW] Minimum iOS Deployment version is now iOS 5.0 - - [NEW] Migrated to use ARC - - [NEW] Added localizations provided by [Wordcrafts.de](http://wordcrafts.de): - Chinese, English, French, German, Italian, Japanese, Portuguese, Brazilian-Portuguese, Russian, Spanish - - [NEW] Added Romanian, Hungarian localization - - [UPDATE] Updated integration and migration documentation - - [Installation & Setup](http://www.hockeyapp.net/help/sdk/ios/3.0.0/docs/docs/Guide-Installation-Setup.html) (Recommended) - - [Installation & Setup Advanced](http://www.hockeyapp.net/help/sdk/ios/3.0.0/docs/docs/Guide-Installation-Setup-Advanced.html) (Using Git submodule and Xcode sub-project) - - [Migration from previous SDK Versions](http://www.hockeyapp.net/help/sdk/ios/3.0.0/docs/docs/Guide-Migration-Kits.html) - - [UPDATE] Using embedded.framework for binary distribution containing everything needed in one package - - [UPDATE] Improved Xcode project setup to only use one static library - - [UPDATE] Providing build settings as `HockeySDK.xcconfig` file for easier setup - - [UPDATE] Remove `-ObjC` from `Other Linker Flags`, since the SDK doesn't need it anymore - - [UPDATE] Improved documentation - - [UPDATE] Excluded binary UUID check from simulator builds, so unit test targets will work. But functionality based on binary UUID cannot be tested in the simulator, e.g. update without changing build version. - - [BUGFIX] Fixed some new compiler warnings - - [BUGFIX] Fixed some missing new lines at EOF - - [BUGFIX] Make sure sure JSON serialization doesn't crash if the string is nil - - [BUGFIX] Various additional minor fixes -

- -- Crash Reporting - - - [NEW] Added anonymous device ID to crash reports - - [UPDATE] The following delegates in `BITCrashManagerDelegate` moved to `BITHockeyManagerDelegate`: - - `- (NSString *)userNameForCrashManager:(BITCrashManager *)crashManager;` is now `- (NSString *)userNameForHockeyManager:(BITHockeyManager *)hockeyManager componentManager:(BITHockeyBaseManager *)componentManager;` - - `- (NSString *)userEmailForCrashManager:(BITCrashManager *)crashManager;` is now `- (NSString *)userEmailForHockeyManager:(BITHockeyManager *)hockeyManager componentManager:(BITHockeyBaseManager *)componentManager;` - - [BUGFIX] Moved calculation of time interval between startup and crash further up in the code, so delegates can use this information e.g. to add it into a log file - - [BUGFIX] If a crash was detected but could not be read (if handling crashes on startup is implemented), the delegate is still called - - [BUGFIX] Timestamp in crash report is now always UTC in en_US locale - - [BUGFIX] Make sure crash reports incident identifier and key don't have special [] chars and some value -

- -- Feedback - - - [NEW] User feedback interface for direct communication with your users - - [NEW] iOS 6 UIActivity component for integrating feedback - - [NEW] When first opening the feedback list view, user details and show compose screen are automatically shown -

- -- Updating - - - [NEW] Support for In-App updates without changing `CFBundleVersion` - - [UPDATE] Update UI modified to be more iOS 6 alike - - [UPDATE] Update UI shows the company name next to the app name if defined in the backend - - [UPDATE] Updated integration and migration documentation: [Installation & Setup](http://www.hockeyapp.net/help/sdk/ios/3.0.0/docs/docs/Guide-Installation-Setup.html) (Recommended), [Installation & Setup Advanced](http://www.hockeyapp.net/help/sdk/ios/3.0.0/docs/docs/Guide-Installation-Setup-Advanced.html) (Using Git submodule and Xcode sub-project), [Migration from previous SDK Versions](http://www.hockeyapp.net/help/sdk/ios/3.0.0/docs/docs/Guide-Migration-Kits.html) - - - [BUGFIX] Fixed a problem showing the update UI animated if there TTNavigator class is present even though not being used - ---- - -### Version 3.0.0 RC 1 - -- General: - - - [NEW] Added localizations provided by [Wordcrafts.de](http://wordcrafts.de): - Chinese, English, French, German, Italian, Japanese, Portuguese, Brazilian-Portuguese, Russian, Spanish - - [NEW] Added Romanian localization - - [UPDATE] Documentation improvements - - [UPDATE] Exclude binary UUID check from simulator builds, so unit test targets will work. But functionality based on binary UUID cannot be tested in the simulator, e.g. update without changing build version. - - [BUGFIX] Cocoapods bugfix for preprocessor definitions - - [BUGFIX] Various additional minor fixes - -- Feedback: - - - [UPDATE] Only push user details screen automatically onto the list view once - - [BUGFIX] Show proper missing user name or email instead of showing `(null)` in a button - - [BUGFIX] Various fixes to changing the `requireUserEmail` and `requireUserName` values - - -### Version 3.0.0b5 - -- General: - - - [NEW] Remove `-ObjC` from `Other Linker Flags`, since the SDK doesn't need it - - [NEW] Update localizations (german, croatian) - - [BUGFIX] Fix some new compiler warnings - - [BUGFIX] Fix some missing new lines at EOF - - [BUGFIX] Make sure sure JSON serialization doesn't crash if the string is nil - -- Crash Reporting: - - - [NEW] Add anonymous device ID to crash reports - - [BUGFIX] Move calculation of time interval between startup and crash further up in the code, so delegates can use this information e.g. to add it into a log file - - [BUGFIX] Call delegate also if a crash was detected but could not be read (if handling crashes on startup is implemented) - - [BUGFIX] Format timestamp in crash report to be always UTC in en_US locale - - [BUGFIX] Make sure crash reports incident identifier and key don't have special [] chars and some value - -- Feedback: - - - [NEW] Ask user details and show compose screen automatically on first opening feedback list view - - [BUGFIX] Fix some users own messages re-appearing after deleting them - - [BUGFIX] Problems displaying feedback list view in a navigation hierarchy - -- Updating: - - - [BUGFIX] Fix a problem showing the update UI animated if there TTNavigator class is present even though not being used - -### Version 3.0.0b4 - -- Crash Reporting: - - - [BUGFIX] Fix a crash if `username`, `useremail` or `userid` delegate method returns `nil` and trying to send a crash report - -- Feedback: - - - [BUGFIX] Fix user data UI not always being presented as a form sheet on the iPad - -- Updating: - - - [BUGFIX] Fix a problem showing the update UI animated if there TTNavigator class is present even though not being used - -### Version 3.0.0b3 - -- General: - - - [BUGFIX] Exchange some more prefixes of TTTAttributedLabel class that have been missed out - - [BUGFIX] Fix some new compiler warnings - -- Crash Reporting: - - - [BUGFIX] Format timestamp in crash report to be always UTC in en_US locale - -### Version 3.0.0b2 - -- General: - - - [BUGFIX] Add missing header files to the binary distribution - - [BUGFIX] Add missing new lines of two header files - -### Version 3.0.0b1 - -- General: - - - [NEW] Feedback component - - [NEW] Minimum iOS Deployment version is now iOS 5.0 - - [NEW] Migrated to use ARC - - [UPDATE] Improved Xcode project setup to only use one static library - - [UPDATE] Providing build settings as `HockeySDK.xcconfig` file for easier setup - - [UPDATE] Using embedded.framework for binary distribution containing everything needed in one package - -- Feedback: - - - [NEW] User feedback interface for direct communication with your users - - [NEW] iOS 6 UIActivity component for integrating feedback - -- Updating: - - - [NEW] Support for In-App updates without changing `CFBundleVersion` - - [UPDATE] Update UI modified to be more iOS 6 alike - - [UPDATE] Update UI shows the company name next to the app name if defined in the backend - - -## Version 2.5.5 - -- General: - - - [BUGFIX] Fix some new compiler warnings - -- Crash Reporting: - - - [NEW] Add anonymous device ID to crash reports - - [BUGFIX] Move calculation of time interval between startup and crash further up in the code, so delegates can use this information e.g. to add it into a log file - - [BUGFIX] Call delegate also if a crash was detected but could not be read (if handling crashes on startup is implemented) - - [BUGFIX] Format timestamp in crash report to be always UTC in en_US locale - - [BUGFIX] Make sure crash reports incident identifier and key don't have special [] chars and some value - -- Updating: - - - [BUGFIX] Fix a problem showing the update UI animated if there TTNavigator class is present even though not being used - -## Version 2.5.4 - -- General: - - - Declared as final release, since everything in 2.5.4b3 is working as expected - -### Version 2.5.4b3 - -- General: - - - [NEW] Atlassian JMC support disabled (Use subproject integration if you want it) - -### Version 2.5.4b2 - -- Crash Reporting: - - - [UPDATE] Migrate pre v2.5 auto send user setting - - [BUGFIX] The alert option 'Auto Send' did not persist correctly - -- Updating: - - - [BUGFIX] Authorization option did not persist correctly and caused authorization to re-appear on every cold app start - -### Version 2.5.4b1 - -- General: - - - [NEW] JMC support is removed from binary distribution, requires the compiler preprocessor definition `JIRA_MOBILE_CONNECT_SUPPORT_ENABLED=1` to be linked. Enabled when using the subproject - - [BUGFIX] Fix compiler warnings when using Cocoapods - -- Updating: - - - [BUGFIX] `expiryDate` property not working correctly - -## Version 2.5.3 - -- General: - - - [BUGFIX] Fix checking validity of live identifier not working correctly - -## Version 2.5.2 - -- General: - - - Declared as final release, since everything in 2.5.2b2 is working as expected - -### Version 2.5.2b2 - -- General: - - - [NEW] Added support for armv7s architecture - -- Updating: - - - [BUGFIX] Fix update checks not done when the app becomes active again - - -### Version 2.5.2b1 - -- General: - - - [NEW] Replace categories with C functions, so the `Other Linker Flag` `-ObjC` and `-all_load` won't not be needed for integration - - [BUGFIX] Some code style fixes and missing new lines in headers at EOF - -- Crash Reporting: - - - [NEW] PLCrashReporter framework now linked into the HockeySDK framework, so that won't be needed to be added separately any more - - [NEW] Add some error handler detection to optionally notify the developer of multiple handlers that could cause crashes not to be reported to HockeyApp - - [NEW] Show an error in the console if an older version of PLCrashReporter is linked - - [NEW] Make sure the app doesn't crash if the developer forgot to delete the old PLCrashReporter version and the framework search path is still pointing to it - -- Updating: - - - [BUGFIX] Fix disabling usage tracking and expiry check not working if `checkForUpdateOnLaunch` is set to NO - - [BUGFIX] `disableUpdateManager` wasn't working correctly - - [BUGFIX] If the server doesn't return any app versions, don't handle this as an error, but show a warning in the console when `debugLogging` is enabled - -## Version 2.5.1 - -- General: - - - [BUGFIX] Typo in delegate `shouldUseLiveIdentifier` of `BITHockeyManagerDelegate` - - [BUGFIX] Default updateManager delegate wasn't set - -- Crash Reporting: - - - [BUGFIX] Crash when developer sends the notification `BITHockeyNetworkDidBecomeReachableNotification` - - -## Version 2.5 - -- General: - - - [NEW] Unified SDK for accessing HockeyApp on iOS - - - Requires iOS 4.0 or newer - - - Replaces the previous separate SDKs for iOS: HockeyKit and QuincyKit. - - The previous SDKs are still available and are still working. But future - HockeyApp features will only be integrated in this new unified SDK. - - - Integration either as framework or Xcode subproject using the sourcecode - - Check out [Installation & Setup](Guide-Installation-Setup) - - - [NEW] Cleaned up public interfaces and internal processing all across the SDK - - - [NEW] [AppleDoc](http://gentlebytes.com/appledoc/) based documentation and HowTos - - This allows the documentation to be generated into HTML or DocSet. - -- Crash Reporting: - - - [NEW] Workflow to handle crashes that happen on startup. - - Check out [How to handle crashes on startup](HowTo-Handle-Crashes-On-Startup) for more details. - - - [NEW] Symbolicate iOS calls async-safe on the device - - - [NEW] Single property/option to deactivate, require user to agree submitting and autosubmit - - E.g. implement a settings screen with the three options and set - `[BITCrashManager crashManagerStatus]` to the desired user value. - - - [UPDATED] Updated [PLCrashReporter](https://code.google.com/p/plcrashreporter/) with updates and bugfixes (source available on [GitHub](https://github.com/bitstadium/PLCrashReporter)) - - - [REMOVED] Feedback for Crash Groups Status - - Please keep using QuincyKit for now if you want this feature. This feature needs to be - redesigned on SDK and server side to be more efficient and easier to use. - -- Updating: - - - [NEW] Expire beta versions with a given date - - - [REMOVED] Settings screen - - If you want users to be able not to send analytics data, implement the - `[BITUpdateManagerDelegate updateManagerShouldSendUsageData:]` delegate and return - the value depending on what the user defines in your settings UI. diff --git a/submodules/HockeySDK-iOS/Documentation/Guides/Crash Reporting Not Working.md b/submodules/HockeySDK-iOS/Documentation/Guides/Crash Reporting Not Working.md deleted file mode 100644 index 5ed8718983..0000000000 --- a/submodules/HockeySDK-iOS/Documentation/Guides/Crash Reporting Not Working.md +++ /dev/null @@ -1,26 +0,0 @@ -## Crash Reporting is not working - -This is a checklist to help find the issue if crashes do not appear in HockeyApp or the dialog asking if crashes should be send doesn't appear: - - -1. Check if the `BETA_IDENTIFIER` or `LIVE_IDENTIFIER` matches the App ID in HockeyApp. - -2. Check if CFBundleIdentifier in your Info.plist matches the Bundle Identifier of the app in HockeyApp. HockeyApp accepts crashes only if both the App ID and the Bundle Identifier equal their corresponding values in your plist and source code. - -3. Unless you have set `[BITCrashManager setCrashManagerStatus:]` to `BITCrashManagerStatusAutoSend`: If your app crashes and you start it again, is the alert shown which asks the user to send the crash report? If not, please crash your app again, then connect the debugger and set a break point in `BITCrashManager.m`, method `startManager` to see why the alert is not shown. - -4. Enable the debug logging option and check the output if the Crash Manager gets `Setup`, `Started`, returns no error message and sending the crash report to the server results in no error: - - [[BITHockeyManager shareHockeyManager] setDebugLogEnabled: YES]; - - -5. Make sure Xcode debugger is not attached while causing the app to crash - -6. Are you trying to catch "out of memory crashes"? This is _NOT_ possible! Out of memory crashes are actually kills by the watchdog process. Whenever you kill a process, there is no crash happening. The crash reports for those that you see on iTunes Connect, are arbitrary reports written by the watchdog process that did the kill. So they only system that can provide information about these, is iOS itself. - -7. If you are using `#ifdef (CONFIGURATION_something)`, make sure that the `something` string matches the exact name of your Xcode build configuration. Spaces are not allowed! - -8. Remove or at least disable any other exception handler or crash reporting framework. - -9. If it still does not work, please [contact us](http://support.hockeyapp.net/discussion/new). - diff --git a/submodules/HockeySDK-iOS/Documentation/Guides/Installation & Setup.md b/submodules/HockeySDK-iOS/Documentation/Guides/Installation & Setup.md deleted file mode 100644 index 2ca0440c4c..0000000000 --- a/submodules/HockeySDK-iOS/Documentation/Guides/Installation & Setup.md +++ /dev/null @@ -1,883 +0,0 @@ - [![Build Status](https://www.bitrise.io/app/30bf519f6bd0a5e2/status.svg?token=RKqHc7-ojjLiEFds53d-ZA&branch=master)](https://www.bitrise.io/app/30bf519f6bd0a5e2) -[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) -[![Version](http://cocoapod-badges.herokuapp.com/v/HockeySDK/badge.png)](http://cocoadocs.org/docsets/HockeySDK) - [![Slack Status](https://slack.hockeyapp.net/badge.svg)](https://slack.hockeyapp.net) - -## Version 5.1.2 - -- [Changelog](http://www.hockeyapp.net/help/sdk/ios/5.1.2/docs/docs/Changelog.html) - -**NOTE** If your are using the binary integration of our SDK, make sure that the `HockeySDKResources.bundle` inside the `HockeySDK.embeddedframework`-folder has been added to your application. - -### Feedback and iOS 10 -**4.1.1 and later of the HockeySDK remove the Feedback feature from the default version of the SDK.** -The reason for this is that iOS 10 requires developers to add a usage string to their Info.plist in case they include the photos framework in their app. If this string is missing, the app will be rejected when submitting the app to the app store. As HockeyApp's Feedback feature includes a dependency to the photos framework. This means that if you include HockeyApp into your app, adding the usage string would be a requirement even for developers who don't use the Feedback feature. If you don't use Feedback in your app, simply upgrade HockeySDK to version 4.1.1 or newer. If you are using Feedback, please have a look at the [Feedback section](#feedback). - - -We **strongly** suggest upgrading to version 4.1.1 or a later version of the SDK. Not specifying the usage description string and using previous versions of the HockeySDK-iOS will cause the app to crash at runtime as soon as the user taps the "attach image"-button or in case you have enabled `BITFeedbackObservationModeOnScreenshot`. - -If you are using an older version of the SDK, you must add a `NSPhotoLibraryUsageDescription` to your `Info.plist` to avoid a AppStore rejection during upload of your app (please have a look at the [Feedback section](#feedback)). - -## Introduction - -HockeySDK-iOS implements support for using HockeyApp in your iOS applications. - -The following features are currently supported: - -1. **Collect crash reports:** If your app crashes, a crash log with the same format as from the Apple Crash Reporter is written to the device's storage. If the user starts the app again, they are asked to submit the crash report to HockeyApp. This works for both beta and live apps, i.e. those submitted to the App Store. - -2. **User Metrics:** Understand user behavior to improve your app. Track usage through daily and monthly active users, monitor crash impacted users, as well as customer engagement through session count.You can now track **Custom Events** in your app, understand user actions and see the aggregates on the HockeyApp portal. - -3. **Update Ad-Hoc / Enterprise apps:** The app will check with HockeyApp if a new version for your Ad-Hoc or Enterprise build is available. If yes, it will show an alert view to the user and let them see the release notes, the version history and start the installation process right away. - -4. **Update notification for app store:** The app will check if a new version for your app store release is available. If yes, it will show an alert view to the user and let them open your app in the App Store app. (Disabled by default!) - -5. **Feedback:** Collect feedback from your users from within your app and communicate directly with them using the HockeyApp backend. - -6. **Authenticate:** Identify and authenticate users of Ad-Hoc or Enterprise builds - -This document contains the following sections: - -1. [Requirements](#requirements) -2. [Setup](#setup) -3. [Advanced Setup](#advancedsetup) - 1. [Linking System Frameworks manually](#linkmanually) - 2. [CocoaPods](#cocoapods) - 3. [Carthage](#carthage) - 4. [iOS Extensions](#extensions) - 5. [WatchKit 1 Extensions](#watchkit) - 6. [Crash Reporting](#crashreporting) - 7. [User Metrics](#user-metrics) - 8. [Feedback](#feedback) - 9. [Store Updates](#storeupdates) - 10. [In-App-Updates (Beta & Enterprise only)](#betaupdates) - 11. [Debug information](#debug) -4. [Documentation](#documentation) -5. [Troubleshooting](#troubleshooting) -6. [Contributing](#contributing) - 1. [Development Environment](#developmentenvironment) - 2. [Code of Conduct](#codeofconduct) - 3. [Contributor License](#contributorlicense) -7. [Contact](#contact) - - -## 1. Requirements - -1. We assume that you already have a project in Xcode and that this project is opened in Xcode 8 or later. -2. The SDK supports iOS 8.0 and later. - - -## 2. Setup - -We recommend integration of our binary into your Xcode project to setup HockeySDK for your iOS app. You can also use our interactive SDK integration wizard in HockeyApp for Mac which covers all the steps from below. For other ways to setup the SDK, see [Advanced Setup](#advancedsetup). - -### 2.1 Obtain an App Identifier - -Please see the "[How to create a new app](http://support.hockeyapp.net/kb/about-general-faq/how-to-create-a-new-app)" tutorial. This will provide you with an HockeyApp specific App Identifier to be used to initialize the SDK. - -### 2.2 Download the SDK - -1. Download the latest [HockeySDK-iOS](http://www.hockeyapp.net/releases/) framework which is provided as a zip-File. -2. Unzip the file and you will see a folder called `HockeySDK-iOS`. (Make sure not to use 3rd party unzip tools!) - -### 2.3 Copy the SDK into your projects directory in Finder - -From our experience, 3rd-party libraries usually reside inside a subdirectory (let's call our subdirectory `Vendor`), so if you don't have your project organized with a subdirectory for libraries, now would be a great start for it. To continue our example, create a folder called `Vendor` inside your project directory and move the unzipped `HockeySDK-iOS`-folder into it. - -The SDK comes in four flavors: - - * Default SDK without Feedback: `HockeySDK.embeddedframework` - * Full featured SDK with Feedback: `HockeySDK.embeddedframework` in the subfolder `HockeySDKAllFeatures`. - * Crash reporting only: `HockeySDK.framework` in the subfolder `HockeySDKCrashOnly`. - * Crash reporting only for extensions: `HockeySDK.framework` in the subfolder `HockeySDKCrashOnlyExtension` (which is required to be used for extensions). - -Our examples will use the **default** SDK (`HockeySDK.embeddedframework`). - - - -### 2.4 Add the SDK to the project in Xcode - -> We recommend using Xcode's group-feature to create a group for 3rd-party-libraries similar to the structure of our files on disk. For example, similar to the file structure in 2.3 above, our projects have a group called `Vendor`. - -1. Make sure the `Project Navigator` is visible (⌘+1). -2. Drag & drop `HockeySDK.embeddedframework` from your `Finder` to the `Vendor` group in `Xcode` using the `Project Navigator` on the left side. -3. An overlay will appear. Select `Create groups` and set the checkmark for your target. Then click `Finish`. - - -### 2.5 Modify Code - -**Objective-C** - -1. Open your `AppDelegate.m` file. -2. Add the following line at the top of the file below your own `import` statements: - - ```objc - @import HockeySDK; - ``` - -3. Search for the method `application:didFinishLaunchingWithOptions:` -4. Add the following lines to setup and start the HockeyApp SDK: - - ```objc - [[BITHockeyManager sharedHockeyManager] configureWithIdentifier:@"APP_IDENTIFIER"]; - // Do some additional configuration if needed here - [[BITHockeyManager sharedHockeyManager] startManager]; - [[BITHockeyManager sharedHockeyManager].authenticator authenticateInstallation]; // This line is obsolete in the crash only builds - ``` - -**Swift** - -1. Open your `AppDelegate.swift` file. -2. Add the following line at the top of the file below your own import statements: - - ```swift - import HockeySDK - ``` - -3. Search for the method - - ```swift - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool - ``` - -4. Add the following lines to setup and start the HockeyApp SDK: - - ```swift - BITHockeyManager.shared().configure(withIdentifier: "APP_IDENTIFIER") - BITHockeyManager.shared().start() - BITHockeyManager.shared().authenticator.authenticateInstallation() // This line is obsolete in the crash only builds - - ``` - - -*Note:* The SDK is optimized to defer everything possible to a later time while making sure e.g. crashes on start-up can also be caught and each module executes other code with a delay some seconds. This ensures that `applicationDidFinishLaunching` will process as fast as possible and the SDK will not block the start-up sequence resulting in a possible kill by the watchdog process. - - -**Congratulation, now you're all set to use HockeySDK!** - - -## 3. Advanced Setup - - -### 3.1 Linking System Frameworks manually - -If you are working with an older project which doesn't support clang modules yet or you for some reason turned off the `Enable Modules (C and Objective-C` and `Link Frameworks Automatically` options in Xcode, you have to manually link some system frameworks: - -1. Select your project in the `Project Navigator` (⌘+1). -2. Select your app target. -3. Select the tab `Build Phases`. -4. Expand `Link Binary With Libraries`. -5. Add the following system frameworks, if they are missing: - 1. Default SDK: - + `CoreText` - + `CoreGraphics` - + `Foundation` - + `MobileCoreServices` - + `QuartzCore` - + `QuickLook` - + `Security` - + `SystemConfiguration` - + `UIKit` - + `libc++` - + `libz` - 2. SDK with all features: - + `CoreText` - + `CoreGraphics` - + `Foundation` - + `MobileCoreServices` - + `QuartzCore` - + `QuickLook` - + `Photos` - + `Security` - + `SystemConfiguration` - + `UIKit` - + `libc++` - + `libz` - 3. Crash reporting only: - + `Foundation` - + `Security` - + `SystemConfiguration` - + `UIKit` - + `libc++` - 4. Crash reporting only for extensions: - + `Foundation` - + `Security` - + `SystemConfiguration` - + `libc++` - -Note that not using clang modules also means that you can't use the `@import` syntax mentioned in the [Modify Code](#modify) section but have to stick to the old `#import ` imports. - - -### 3.2 CocoaPods - -[CocoaPods](http://cocoapods.org) is a dependency manager for Objective-C, which automates and simplifies the process of using 3rd-party libraries like HockeySDK in your projects. To learn how to setup CocoaPods for your project, visit the [official CocoaPods website](http://cocoapods.org/). - -**Podfile** - -```ruby -platform :ios, '8.0' -pod "HockeySDK" -``` - -#### 3.2.1 Binary Distribution Options - -The default and recommended distribution is a binary (static library) and a resource bundle with translations and images for all SDK features. - -```ruby -platform :ios, '8.0' -pod "HockeySDK" -``` - -Will integrate the *default* configuration of the SDK, with all features except the Feedback feature. - -For the SDK with all features, including Feedback, add - -```ruby -pod "HockeySDK", :subspecs => ['AllFeaturesLib'] -``` -to your podfile. - -To add the variant that only includes crash reporting, use - -```ruby -pod "HockeySDK", :subspecs => ['CrashOnlyLib'] -``` - -Or you can use the Crash Reporting build only for extensions by using the following line in your `Podfile`: - -```ruby -pod "HockeySDK", :subspecs => ['CrashOnlyExtensionsLib'] -``` - -#### 3.2.2 Source Integration Options - -Alternatively, you can integrate the SDK by source if you want to do modifications or want a different feature set. The following entry will integrate the SDK: - -```ruby -pod "HockeySDK-Source" -``` - - -### 3.3 Carthage - -[Carthage](https://github.com/Carthage/Carthage) is an alternative way to add frameworks to your app. For general information about how to use Carthage, please follow their [documentation](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application). - -To add HockeySDK to your project, simply put this line into your `Cartfile`: - -`github "bitstadium/HockeySDK-iOS"` - -and then follow the steps described in the [Carthage documentation](https://github.com/Carthage/Carthage#if-youre-building-for-ios-tvos-or-watchos). - -This will integrate the **full-featured SDK** so you must include the `NSPhotoLibraryUsageDescription` and read the [feedback section](#feedback). If you want to include any other version of the SDK, version 4.1.4 added the ability to do that. You need to specify the configuration that you want to use - -#### Version without Feedback - -`carthage build --platform iOS --configuration ReleaseDefault HockeySDK-iOS` - -#### Crash-only version - -`carthage build --platform iOS --configuration ReleaseCrashOnly HockeySDK-iOS` - -### Crash-only extension - -`carthage build --platform iOS --configuration ReleaseCrashOnlyExtension HockeySDK-iOS` - - -### 3.4 iOS Extensions - -The following points need to be considered to use the HockeySDK SDK with iOS Extensions: - -1. Each extension is required to use the same values for version (`CFBundleShortVersionString`) and build number (`CFBundleVersion`) as the main app uses. (This is required only if you are using the same `APP_IDENTIFIER` for your app and extensions). -2. You need to make sure the SDK setup code is only invoked **once**. Since there is no `applicationDidFinishLaunching:` equivalent and `viewDidLoad` can run multiple times, you need to use a setup like the following example: - -**Objective-C** - - ```objc - static BOOL didSetupHockeySDK = NO; - - @interface TodayViewController () - - @end - - @implementation TodayViewController - - + (void)viewDidLoad { - [super viewDidLoad]; - if (!didSetupHockeySDK) { - [[BITHockeyManager sharedHockeyManager] configureWithIdentifier:@"APP_IDENTIFIER"]; - [[BITHockeyManager sharedHockeyManager] startManager]; - didSetupHockeySDK = YES; - } - } - ``` - - **Swift** - - ```swift - class TodayViewController: UIViewController, NCWidgetProviding { - - static var didSetupHockeySDK = false; - - override func viewDidLoad() { - super.viewDidLoad() - if !TodayViewController.didSetupHockeySDK { - BITHockeyManager.shared().configure(withIdentifier: "APP_IDENTIFIER") - BITHockeyManager.shared().start() - TodayViewController.didSetupHockeySDK = true - } - } - } - ``` - -3. The binary distribution provides a special framework build in the `HockeySDKCrashOnly` or `HockeySDKCrashOnlyExtension` folder of the distribution zip file, which only contains crash reporting functionality (also automatic sending crash reports only). - - -### 3.5 WatchKit 1 Extensions - -The following points need to be considered to use HockeySDK with WatchKit 1 Extensions: - -1. WatchKit extensions don't use regular `UIViewControllers` but rather `WKInterfaceController` subclasses. These have a different lifecycle than you might be used to. - - To make sure that the HockeySDK is only instantiated once in the WatchKit extension's lifecycle we recommend using a helper class similar to this: - - **Objective-C** - - ```objc - @import Foundation; - - @interface BITWatchSDKSetup : NSObject - - * (void)setupHockeySDKIfNeeded; - - @end - ``` - - ```objc - #import "BITWatchSDKSetup.h" - @import HockeySDK - - static BOOL hockeySDKIsSetup = NO; - - @implementation BITWatchSDKSetup - - * (void)setupHockeySDKIfNeeded { - if (!hockeySDKIsSetup) { - [[BITHockeyManager sharedHockeyManager] configureWithIdentifier:@"APP_IDENTIFIER"]; - [[BITHockeyManager sharedHockeyManager] startManager]; - hockeySDKIsSetup = YES; - } - } - - @end - ``` - - **Swift** - - ```swift - import HockeySDK - - class BITWatchSDKSetup { - - static var hockeySDKIsSetup = false; - - static func setupHockeySDKIfNeeded() { - if !BITWatchSDKSetup.hockeySDKIsSetup { - BITHockeyManager.shared().configure(withIdentifier: "APP_IDENTIFIER") - BITHockeyManager.shared().start() - BITWatchSDKSetup.hockeySDKIsSetup = true; - } - } - } - ``` - - - Then, in each of your WKInterfaceControllers where you want to use the HockeySDK, you should do this: - - **Objective-C** - - ```objc - #import "InterfaceController.h" - @import HockeySDK - #import "BITWatchSDKSetup.h" - - @implementation InterfaceController - - + (void)awakeWithContext:(id)context { - [super awakeWithContext:context]; - [BITWatchSDKSetup setupHockeySDKIfNeeded]; - } - - + (void)willActivate { - [super willActivate]; - } - - + (void)didDeactivate { - [super didDeactivate]; - } - - @end - ``` - - **Swift** - - ```swift - class InterfaceController: WKInterfaceController { - - override func awake(withContext context: Any?) { - super.awake(withContext: context) - BITWatchSDKSetup.setupHockeySDKIfNeeded() - } - - override func willActivate() { - super.willActivate() - } - - override func didDeactivate() { - super.didDeactivate() - } - - } - ``` -2. The binary distribution provides a special framework build in the `HockeySDKCrashOnly` or `HockeySDKCrashOnlyExtension` folder of the distribution zip file, which only contains crash reporting functionality (also automatic sending crash reports only). - - -### 3.6 Crash Reporting - -The following options only show some of the possibilities to interact and fine-tune the crash reporting feature. For more please check the full documentation of the `BITCrashManager` class in our [documentation](#documentation). - -#### 3.6.1 Disable Crash Reporting -The HockeySDK enables crash reporting **per default**. Crashes will be immediately sent to the server the next time the app is launched. - -To provide you with the best crash reporting, we are using a build of [PLCrashReporter]("https://github.com/plausiblelabs/plcrashreporter") based on [Version 1.2.1 / Commit 356901d7f3ca3d46fbc8640f469304e2b755e461]("https://github.com/plausiblelabs/plcrashreporter/commit/356901d7f3ca3d46fbc8640f469304e2b755e461"). - -This feature can be disabled as follows: - -**Objective-C** - -```objc -[[BITHockeyManager sharedHockeyManager] configureWithIdentifier:@"APP_IDENTIFIER"]; - -[[BITHockeyManager sharedHockeyManager] setDisableCrashManager: YES]; //disable crash reporting - -[[BITHockeyManager sharedHockeyManager] startManager]; -``` - -**Swift** - -```swift -BITHockeyManager.shared().configure(withIdentifier: "APP_IDENTIFIER") -BITHockeyManager.shared().isCrashManagerDisabled = true -BITHockeyManager.shared().start() -``` -#### 3.6.2 Auto send crash reports - -Crashes are send the next time the app starts. If `crashManagerStatus` is set to `BITCrashManagerStatusAutoSend`, crashes will be send without any user interaction, otherwise an alert will appear allowing the users to decide whether they want to send the report or not. - -**Objective-C** - -```objc -[[BITHockeyManager sharedHockeyManager] configureWithIdentifier:@"APP_IDENTIFIER"]; - -[[BITHockeyManager sharedHockeyManager].crashManager setCrashManagerStatus: BITCrashManagerStatusAutoSend]; - -[[BITHockeyManager sharedHockeyManager] startManager]; -``` - -**Swift** - -```swift -BITHockeyManager.shared().configure(withIdentifier: "APP_IDENTIFIER") -BITHockeyManager.shared().crashManager.crashManagerStatus = BITCrashManagerStatus.autoSend -BITHockeyManager.shared().start() -``` - -The SDK is not sending the reports right when the crash happens deliberately, because if is not safe to implement such a mechanism while being async-safe (any Objective-C code is _NOT_ async-safe!) and not causing more danger like a deadlock of the device, than helping. We found that users do start the app again because most don't know what happened, and you will get by far most of the reports. - -Sending the reports on start-up is done asynchronously (non-blocking). This is the only safe way to ensure that the app won't be possibly killed by the iOS watchdog process, because start-up could take too long and the app could not react to any user input when network conditions are bad or connectivity might be very slow. - -#### 3.6.3 Mach Exception Handling - -By default the SDK is using the safe and proven in-process BSD Signals for catching crashes. This option provides an option to enable catching fatal signals via a Mach exception server instead. - -We strongly advise _NOT_ to enable Mach exception handler in release versions of your apps! - -*Warning:* The Mach exception handler executes in-process, and will interfere with debuggers when they attempt to suspend all active threads (which will include the Mach exception handler). Mach-based handling should _NOT_ be used when a debugger is attached. The SDK will not enable catching exceptions if the app is started with the debugger running. If you attach the debugger during runtime, this may cause issues the Mach exception handler is enabled! - -**Objective-C** - -```objc -[[BITHockeyManager sharedHockeyManager] configureWithIdentifier:@"APP_IDENTIFIER"]; - -[[BITHockeyManager sharedHockeyManager].crashManager setEnableMachExceptionHandler: YES]; - -[[BITHockeyManager sharedHockeyManager] startManager]; -``` - -**Swift** - -```swift -BITHockeyManager.shared().configure(withIdentifier: "APP_IDENTIFIER") -BITHockeyManager.shared().crashManager.isMachExceptionHandlerEnabled = true -BITHockeyManager.shared().start() -``` - -#### 3.6.4 Attach additional data - -The `BITHockeyManagerDelegate` protocol provides methods to add additional data to a crash report: - -1. UserID: - -**Objective-C** - -`- (NSString *)userIDForHockeyManager:(BITHockeyManager *)hockeyManager componentManager:(BITHockeyBaseManager *)componentManager;` - -**Swift** - -`optional public func userID(for hockeyManager: BITHockeyManager!, componentManager: BITHockeyBaseManager!) -> String!` - -2. UserName: - -**Objective-C** - -`- (NSString *)userNameForHockeyManager:(BITHockeyManager *)hockeyManager componentManager:(BITHockeyBaseManager *)componentManager;` - -**Swift** - -`optional public func userName(for hockeyManager: BITHockeyManager!, componentManager: BITHockeyBaseManager!) -> String!` - -3. UserEmail: - -**Objective-C** - -`- (NSString *)userEmailForHockeyManager:(BITHockeyManager *)hockeyManager componentManager:(BITHockeyBaseManager *)componentManager;` - -**Swift** - -`optional public func userEmail(for hockeyManager: BITHockeyManager!, componentManager: BITHockeyBaseManager!) -> String!` - -The `BITCrashManagerDelegate` protocol (which is automatically included in `BITHockeyManagerDelegate`) provides methods to add more crash specific data to a crash report: - -1. Text attachments: - -**Objective-C** - -`-(NSString *)applicationLogForCrashManager:(BITCrashManager *)crashManager` - -**Swift** - -`optional public func applicationLog(for crashManager: BITCrashManager!) -> String!` - - Check the following tutorial for an example on how to add CocoaLumberjack log data: [How to Add Application Specific Log Data on iOS or OS X](http://support.hockeyapp.net/kb/client-integration-ios-mac-os-x/how-to-add-application-specific-log-data-on-ios-or-os-x) -2. Binary attachments: - -**Objective-C** - -`-(BITHockeyAttachment *)attachmentForCrashManager:(BITCrashManager *)crashManager` - -**Swift** - -`optional public func attachment(for crashManager: BITCrashManager!) -> BITHockeyAttachment!` - -Make sure to implement the protocol - -**Objective-C** - -```objc -@interface YourAppDelegate () {} - -@end -``` - -**Swift** - -```swift -class YourAppDelegate: BITHockeyManagerDelegate { - -} -``` - -and set the delegate: - -**Objective-C** - -```objc -[[BITHockeyManager sharedHockeyManager] configureWithIdentifier:@"APP_IDENTIFIER"]; - -[[BITHockeyManager sharedHockeyManager] setDelegate: self]; - -[[BITHockeyManager sharedHockeyManager] startManager]; -``` - -**Swift** - -```swift -BITHockeyManager.shared().configure(withIdentifier: "APP_IDENTIFIER") -BITHockeyManager.shared().delegate = self -BITHockeyManager.shared().start() -``` - - -### 3.7 User Metrics - -HockeyApp automatically provides you with nice, intelligible, and informative metrics about how your app is used and by whom. - -- **Sessions**: A new session is tracked by the SDK whenever the containing app is restarted (this refers to a 'cold start', i.e., when the app has not already been in memory prior to being launched) or whenever it becomes active again after having been in the background for 20 seconds or more. -- **Users**: The SDK anonymously tracks the users of your app by creating a random UUID that is then securely stored in the iOS keychain. This anonymous ID is stored in the keychain, as of iOS 10, it no longer persists across re-installations. -- **Custom Events**: With HockeySDK 4.1.0 and later, you can now track Custom Events in your app, understand user actions and see the aggregates on the HockeyApp portal. -- **Batching & offline behavior**: The SDK batches up to 50 events or waits for 15s and then persist and send the events, whichever comes first. So for sessions, this might actually mean we send 1 single event per batch. If you are sending Custom Events, it can be 1 session event plus X of your Custom Events (up to 50 events per batch total). In case the device is offline, up to 300 events are stored until the SDK starts to drop new events. - -Just in case you want to opt-out of the automatic collection of anonymous users and sessions statistics, there is a way to turn this functionality off at any time: - -**Objective-C** - -```objc -[BITHockeyManager sharedHockeyManager].disableMetricsManager = YES; -``` - -**Swift** - -```swift -BITHockeyManager.shared().isMetricsManagerDisabled = true -``` - -#### 3.7.1 Custom Events - -By tracking custom events, you can now get insight into how your customers use your app, understand their behavior and answer important business or user experience questions while improving your app. - -- Before starting to track events, ask yourself the questions that you want to get answers to. For instance, you might be interested in business, performance/quality or user experience aspects. -- Name your events in a meaningful way and keep in mind that you will use these names when searching for events in the HockeyApp web portal. It is your responsibility to not collect personal information as part of the events tracking. - -**Objective-C** - -```objc -BITMetricsManager *metricsManager = [BITHockeyManager sharedHockeyManager].metricsManager; - -[metricsManager trackEventWithName:eventName] -``` - -**Swift** - -```swift -let metricsManager = BITHockeyManager.shared().metricsManager - -metricsManager.trackEvent(withName: eventName) -``` - -**Limitations** - -- Accepted characters for tracking events are: [a-zA-Z0-9_. -]. If you use other than the accepted characters, your events will not show up in the HockeyApp web portal. -- There is currently a limit of 300 unique event names per app per week. -- There is _no_ limit on the number of times an event can happen. - -#### 3.7.2 Attaching custom properties and measurements to a custom event - -It's possible to attach properties and/or measurements to a custom event. There is one limitation to attaching properties and measurements. They currently don't show up in the HockeyApp dashboard but you have to link your app to Application Insights to be able to query them. Please have a look at [our blog post](https://www.hockeyapp.net/blog/2016/08/30/custom-events-public-preview.html) to find out how to do that. - -- Properties have to be a string. -- Measurements have to be of a numeric type. - -**Objective-C** - -```objc -BITMetricsManager *metricsManager = [BITHockeyManager sharedHockeyManager].metricsManager; - -NSDictionary *myProperties = @{@"Property 1" : @"Something", - @"Property 2" : @"Other thing", - @"Property 3" : @"Totally different thing"}; -NSDictionary *myMeasurements = @{@"Measurement 1" : @1, - @"Measurement 2" : @2.34, - @"Measurement 3" : @2000000}; - -[metricsManager trackEventWithName:eventName properties:myProperties measurements:myMeasurements] -``` - -**Swift** - -```swift -let myProperties = ["Property 1": "Something", "Property 2": "Other thing", "Property 3" : "Totally different thing."] -let myMeasurements = ["Measurement 1": 1, "Measurement 2": 2.3, "Measurement 3" : 30000] - -let metricsManager = BITHockeyManager.shared().metricsManager -metricsManager.trackEvent(withName: eventName, properties: myProperties, measurements: myMeasurements) -``` - - -### 3.8 Feedback - -As of HockeySDK 4.1.1, Feedback is no longer part of the default SDK. To use feedback in your app, integrate the SDK with all features as follows: - -#### 3.8.1 Integrate the full-featured SDK. - -If you're integrating the binary yourself, use the `HockeySDK.embeddedframework` in the subfolder `HockeySDKAllFeatures`. If you're using CocoaPods, use - -```ruby -pod "HockeySDK", :subspecs => ['AllFeaturesLib'] -``` - -in your podfile. - -`BITFeedbackManager` lets your users communicate directly with you via the app and an integrated user interface. It provides a single threaded discussion with a user running your app. This feature is only enabled if you integrate the actual view controllers into your app. - -You should never create your own instance of `BITFeedbackManager` but use the one provided by the `[BITHockeyManager sharedHockeyManager]`: - -**Objective-C** - -```objc -[BITHockeyManager sharedHockeyManager].feedbackManager -``` - -**Swift** - -```swift -BITHockeyManager.shared().feedbackManager -``` - -Please check the [documentation](#documentation) of the `BITFeedbackManager` and `BITFeedbackManagerDelegate` classes on more information on how to leverage this feature. - -#### 3.8.2 Add the NSPhotoLibraryUsageDescription to your Info.plist. - -As of iOS 10, developers have to add UsageDescription-strings before using system frameworks with privacy features (read more on this in [Apple's own documentation](https://developer.apple.com/library/prerelease/content/releasenotes/General/WhatsNewIniOS/Articles/iOS10.html#//apple_ref/doc/uid/TP40017084-SW3)). To make allow users to attach photos to feedback, add the `NSPhotoLibraryUsageDescription` to your `Info.plist` and provide a description. Make sure to localize your description as described in [Apple's documentation about localizing Info.plist strings](https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/AboutInformationPropertyListFiles.html). - -If the value is missing from your `Info.plist`, the SDK will disable attaching photos to feedback and disable the creation of a new feedback item in case of a screenshot. - - - -### 3.9 Store Updates - -This is the HockeySDK module for handling app updates when having your app released in the App Store. - -When an update is detected, this module will show an alert asking the user if he/she wants to update or ignore this version. If the update was chosen, it will open the apps page in the app store app. - -By default this module is **NOT** enabled! To enable it use the following code: - -**Objective-C** - -```objc -[[BITHockeyManager sharedHockeyManager] configureWithIdentifier:@"APP_IDENTIFIER"]; - -[[BITHockeyManager sharedHockeyManager] setEnableStoreUpdateManager: YES]; - -[[BITHockeyManager sharedHockeyManager] startManager]; -``` - -**Swift** - -```swift -BITHockeyManager.shared().configure(withIdentifier: "APP_IDENTIFIER") -BITHockeyManager.shared().isStoreUpdateManagerEnabled = true -BITHockeyManager.shared().start() -``` - -When this module is enabled and **NOT** running in an App Store build/environment, it won't do any checks! - -Please check the [documentation](#documentation) of the `BITStoreUpdateManager` class on more information on how to leverage this feature and know about its limits. - - -### 3.10 In-App-Updates (Beta & Enterprise only) - -The following options only show some of the possibilities to interact and fine-tune the update feature when using Ad-Hoc or Enterprise provisioning profiles. For more please check the full documentation of the `BITUpdateManager` class in our [documentation](#documentation). - -The feature handles version updates, presents the update and version information in a App Store like user interface, collects usage information and provides additional authorization options when using Ad-Hoc provisioning profiles. - -This module automatically disables itself when running in an App Store build by default! - -This feature can be disabled manually as follows: - -**Objective-C** - -```objc -[[BITHockeyManager sharedHockeyManager] configureWithIdentifier:@"APP_IDENTIFIER"]; - -[[BITHockeyManager sharedHockeyManager] setDisableUpdateManager: YES]; //disable auto updating - -[[BITHockeyManager sharedHockeyManager] startManager]; -``` - -**Swift** - -```swift -BITHockeyManager.shared().configure(withIdentifier: "APP_IDENTIFIER") -BITHockeyManager.shared().isUpdateManagerDisabled = true -BITHockeyManager.shared().start() -``` - -Please note that the SDK expects your CFBundleVersion values to always increase and never reset to detect a new update. - -If you want to see beta analytics, use the beta distribution feature with in-app updates, restrict versions to specific users, or want to know who is actually testing your app, you need to follow the instructions on our guide [Authenticating Users on iOS](http://support.hockeyapp.net/kb/client-integration-ios-mac-os-x/authenticating-users-on-ios) - - -### 3.11 Debug information - -To check if data is send properly to HockeyApp and also see some additional SDK debug log data in the console, add the following line before `startManager`: - -```objc -[[BITHockeyManager sharedHockeyManager] configureWithIdentifier:@"APP_IDENTIFIER"]; - -[BITHockeyManager sharedHockeyManager].logLevel = BITLogLevelDebug; - -[[BITHockeyManager sharedHockeyManager] startManager]; -``` - - -## 4. Documentation - -Our documentation can be found on [HockeyApp](http://hockeyapp.net/help/sdk/ios/5.1.2/index.html). - - -## 5.Troubleshooting - -### Linker warnings - - Make sure that all mentioned frameworks and libraries are linked - -### iTunes Connect rejection - - Make sure none of the following files are copied into your app bundle, check under app target, `Build Phases`, `Copy Bundle Resources` or in the `.app` bundle after building: - - - `HockeySDK.framework` (except if you build a dynamic framework version of the SDK yourself!) - - `de.bitstadium.HockeySDK-iOS-5.1.2.docset` - -### Features are not working as expected - - Enable debug output to the console to see additional information from the SDK initializing the modules, sending and receiving network requests and more by adding the following code before calling `startManager`: - - `[BITHockeyManager sharedHockeyManager].logLevel = BITLogLevelDebug;` - -### Wrong strings or "Missing HockeySDKResources.bundle" error - -1. Please check if the `HockeySDKResources.bundle` is added to your app bundle. Use Finder to inspect your `.app` bundle to see if the bundle is added. - -2. If it is missing, please check if the resources bundle is mentioned in your app target's `Copy Bundle Resources` build step in the `Build Phases` tab. Add the resource bundle manually if necessary. - -3. Make a clean build and try again. - -![Screenshot_2015-12-22_01.07.27.png](https://support.hockeyapp.net/help/assets/0e9d2eb58de8355363b89bd491d6fcf4c14f596e/normal/Screenshot_2015-12-22_01.07.27.png) - - -## 6. Contributing - -We're looking forward to your contributions via pull requests on our [GitHub repository](https://github.com/bitstadium/HockeySDK-iOS). - - -### 6.1 Development environment - -* A Mac running the latest version of macOS. -* Get the latest Xcode from the Mac App Store. -* [Jazzy](https://github.com/realm/jazzy) to generate documentation. -* [CocoaPods](https://cocoapods.org/) to test integration with CocoaPods. -* [Carthage](https://github.com/Carthage/Carthage) to test integration with Carthage. - - -### 6.2 Code of Conduct - -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. - - -### 6.3 Contributor License - -You must sign a [Contributor License Agreement](https://cla.microsoft.com/) before submitting your pull request. To complete the Contributor License Agreement (CLA), you will need to submit a request via the [form](https://cla.microsoft.com/) and then electronically sign the CLA when you receive the email containing the link to the document. You need to sign the CLA only once to cover submission to any Microsoft OSS project. - - -## 7. Contact - -If you have further questions or are running into trouble that cannot be resolved by any of the steps here, feel free to open [a GitHub issue](https://github.com/bitstadium/HockeySDK-iOS/issues), contact us at [support@hockeyapp.net](mailto:support@hockeyapp.net) or join our [Slack](https://slack.hockeyapp.net). diff --git a/submodules/HockeySDK-iOS/Documentation/Guides/Migration Kits.md b/submodules/HockeySDK-iOS/Documentation/Guides/Migration Kits.md deleted file mode 100644 index d8e474e936..0000000000 --- a/submodules/HockeySDK-iOS/Documentation/Guides/Migration Kits.md +++ /dev/null @@ -1,189 +0,0 @@ -## Introduction - -This guide will help you migrate from QuincyKit, HockeyKit or an older version of HockeySDK-iOS to the latest release of the unified HockeySDK for iOS. - -First of all we will cleanup the obsolete installation files and then convert your existing code to the new API calls. - -## Cleanup - -First of all you should remove all files from prior versions of either QuincyKit, HockeyKit or HockeySDK-iOS. If you not sure which files you added, here are a few easy steps for each SDK. - -### QuincyKit - -In Xcode open the `Project Navigator` (⌘+1). In the search field at the bottom enter "Quincy". QuincyKit is installed, if search finds the following files: - -* BWQuincyManager.h -* BWQuincyManager.m -* Quincy.bundle - -Delete them all ("Move to Trash"). Or if you have them grouped into a folder (for example Vendor/QuincyKit) delete the folder. - -### HockeyKit - -In Xcode open the `Project Navigator` (⌘+1). In the search field at the bottom enter "Hockey". HockeyKit is installed, if search finds for example: - -* BWHockeyManager.h -* Hockey.bundle - -All of them should be in one folder/group in Xcode. Remove that folder. - -### HockeySDK-iOS before v2.5 - -In Xcode open the `Project Navigator` (⌘+1). In the search field at the bottom enter "CNSHockeyManager". If search returns any results you have the first release of our unified SDK added to your project. Even if you added it as a git submodule we would suggest you remove it first. - -### HockeySDK-iOS v2.5.x - -In Xcode open the `Project Navigator` (⌘+1). In the search field at the bottom enter `HockeySDK.framework`. If search returns any results you have the first release of our unified SDK added to your project. Even if you added it as a git submodule we would suggest you remove it first. Repeat the same for `CrashReporter.framework` and `HockeySDKResources.bundle`. - -### HockeySDK-iOS v3.0.x - -In Xcode open the `Project Navigator` (⌘+1). In the search field at the bottom enter `HockeySDK.embeddedFramework`. If search returns any results you have the first release of our unified SDK added to your project. Even if you added it as a git submodule we would suggest you remove it first. - -### Final Steps - -Search again in the `Project Navigator` (⌘+1) for "CrashReporter.framework". You shouldn't get any results now. If not, remove the CrashReporter.framework from your project. - -## Installation - -Follow the steps in our installation guide for either [Installation with binary framework distribution](http://support.hockeyapp.net/kb/client-integration/hockeyapp-for-ios-hockeysdk#framework) (Recommended) or [Installation as a subproject](http://support.hockeyapp.net/kb/client-integration/hockeyapp-for-ios-hockeysdk#subproject) - -After you finished the steps for either of the installation procedures, we have to migrate your existing code. - -## Setup - -### QuincyKit / HockeyKit - -In your application delegate (for example `AppDelegate.m`) search for the following lines: - - ```objc - [[BWQuincyManager sharedQuincyManager] setAppIdentifier:@"0123456789abcdef"]; - - [[BWHockeyManager sharedHockeyManager] setAppIdentifier:@"0123456789abcdef"]; - [[BWHockeyManager sharedHockeyManager] setUpdateURL:@"https://rink.hockeyapp.net/"]; - ``` - -If you use (as recommended) different identifiers for beta and store distribution some lines may be wrapped with compiler macros like this: - - ```objc - #if defined (CONFIGURATION_Beta) - [[BWQuincyManager sharedQuincyManager] setAppIdentifier:@"BETA_IDENTIFIER"]; - #endif - - #if defined (CONFIGURATION_Distribution) - [[BWQuincyManager sharedQuincyManager] setAppIdentifier:@"LIVE_IDENTIFIER"]; - #endif - ``` - -For now comment out all lines with either `[BWQuincyManager sharedQuincyManager]` or `[BWHockeyManager sharedHockeyManager]`. - -Open the header file of your application delegate (for example `AppDelegate.m`) or just press ^ + ⌘ + ↑ there should be a line like this (AppDelegate should match the name of the file) - - ```objc - @interface AppDelegate : NSObject { - ``` - -Remove the `BWHockeyManagerDelegate`. Also look for the following line: - - ```objc - #import "BWHockeyManager.h" - ``` - -And remove it too. (This line may have a #if macro around it, remove that too) - -Now follow the steps described in our [setup guide](http://support.hockeyapp.net/kb/client-integration/hockeyapp-for-ios-hockeysdk#setup) The values for `LIVE_IDENTIFIER` and `BETA_IDENTIFIER` are used in the setup guide. - -After you have finished the setup guide make sure everything works as expected and then delete the out commented lines from above. - -### HockeySDK-iOS before 2.5 - -In your application delegate (for example `AppDelegate.m`) search for the following lines: - - ```objc - [[CNSHockeyManager sharedHockeyManager] configureWithBetaIdentifier:BETA_IDENTIFIER - liveIdentifier:LIVE_IDENTIFIER - delegate:self]; - ``` - -For now comment out all lines with `[CNSHockeyManager sharedHockeyManager]`. Open the header file of your application delegate by pressing ^ + ⌘ + ↑. There should be a line like this: - - ```objc - @interface AppDelegate : NSObject { - ``` - -Remove `CNSHockeyManagerDelegate`, also look for this line: - - ```objc - #import "CNSHockeyManager.h" - ``` - -And remove that too. - -Now follow the steps described in our [setup guide](http://support.hockeyapp.net/kb/client-integration/hockeyapp-for-ios-hockeysdk#setup) The values for `LIVE_IDENTIFIER` and `BETA_IDENTIFIER` are used in the setup guide. - -After you have finished the setup guide make sure everything works as expected and then delete the out commented lines from above. - -### HockeySDK-iOS 2.5.x - -There are no changes to the SDK setup code required. Some delegates methods are deprecated and should be replaced as soon as feasible. - -The following delegates in `BITCrashManagerDelegate` moved to `BITHockeyManagerDelegate`: - -- `- (NSString *)userNameForCrashManager:(BITCrashManager *)crashManager;` is now `- (NSString *)userNameForHockeyManager:(BITHockeyManager *)hockeyManager componentManager:(BITHockeyBaseManager *)componentManager;` -- `- (NSString *)userEmailForCrashManager:(BITCrashManager *)crashManager;` is now `- (NSString *)userEmailForHockeyManager:(BITHockeyManager *)hockeyManager componentManager:(BITHockeyBaseManager *)componentManager;` - -### HockeySDK-iOS 3.0.x - -Instead of implementing the individual protocols in your app delegate, you can now simply add `BITHockeyManagerDelegate` alone, e.g.: - - ```objc - @interface BITAppDelegate () {} - - @end - ``` - -The delegate `-(NSString *)customDeviceIdentifierForUpdateManager:(BITUpdateManager *)updateManager` has been removed. To identify the installation please use the new `BITAuthenticator` class. - -### HockeySDK-iOS 3.5.x - -If you are using `PLCrashReporterCallbacks`, you now have to use `BITCrashManagerCallbacks` instead. This `struct` doesn't contain `version` any longer, so you have to remove that. Otherwise everything is the same. - -If you did set the delegate per component, e.g. `[[BITHockeyManager sharedHockeyManager].crashManager setDelegate:self]`, you need to remove these and set the delegate this way only: `[[BITHockeyManager sharedHockeyManager] setDelegate:self]`. This will propagate the delegate to all SDK components. Make sure to set it before calling `startManager`! - -In addition you need to make sure all of these frameworks are linked: - -- `AssetsLibrary` -- `CoreText` -- `CoreGraphics` -- `Foundation` -- `MobileCoreServices` -- `QuartzCore` -- `QuickLook` -- `Security` -- `SystemConfiguration` -- `UIKit` - -### HockeySDK-iOS 3.7.x - -You need to make sure all of these frameworks are linked: - -- `AssetsLibrary` -- `CoreText` -- `CoreGraphics` -- `Foundation` -- `MobileCoreServices` -- `QuartzCore` -- `QuickLook` -- `Security` -- `SystemConfiguration` -- `UIKit` -- `libc++` - -## Troubleshooting - -### ld: warning: directory not found for option '....QuincyKit.....' - -This warning means there is still a `Framework Search Path` pointing to the folder of the old SDK. Open the `Project Navigator` (⌘+1) and go to the tab `Build Settings`. In the search field enter the name of the folder mentioned in the warning (for example "QuincyKit") . If the search finds something in `Framework Search Paths` you should double click that entry and remove the line which points to the old folder. - -## Advanced Migration - -If you used any optional API calls, for example adding a custom description to a crash report, migrating those would exceed the scope of this guide. Please have a look at the [API documentation](http://hockeyapp.net/releases/). diff --git a/submodules/HockeySDK-iOS/Documentation/Guides/Set Custom AlertViewHandler.md b/submodules/HockeySDK-iOS/Documentation/Guides/Set Custom AlertViewHandler.md deleted file mode 100644 index b11ea780ea..0000000000 --- a/submodules/HockeySDK-iOS/Documentation/Guides/Set Custom AlertViewHandler.md +++ /dev/null @@ -1,133 +0,0 @@ -## Introduction - -HockeySDK lets the user decide wether to send a crash report or lets the developer send crash reports automatically without user interaction. In addition it is possible to attach more data like logs, a binary, or the users name, email or a user ID if this is already known. - -Starting with HockeySDK version 3.6 it is possible to customize this even further and implement your own flow to e.g. ask the user for more details about what happened or his name and email address if your app doesn't know that yet. - -The following example shows how this could be implemented. We'll present a custom UIAlertView asking the user for more details and attaching that to the crash report. - -## HowTo - -1. Setup the SDK -2. Configure HockeySDK to use your custom alertview handler using the `[[BITHockeyManager sharedHockeyManager].crashManager setAlertViewHandler:(BITCustomAlertViewHandler)alertViewHandler;` method in your AppDelegate. -3. Implement your handler in a way that it calls `[[BITHockeyManager sharedHockeyManager].crashManagerhandleUserInput:(BITCrashManagerUserInput)userInput withUserProvidedMetaData:(BITCrashMetaData *)userProvidedMetaData]` with the input provided by the user. -4. Dismiss your custom view. - -## Example - -**Objective-C** - -```objc -@interface BITAppDelegate () -@end - - -@implementation BITAppDelegate - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - [self.window makeKeyAndVisible]; - - [[BITHockeyManager sharedHockeyManager] configureWithIdentifier:@"<>" - delegate:nil]; - - // optionally enable logging to get more information about states. - [BITHockeyManager sharedHockeyManager].debugLogEnabled = YES; - - [[BITHockeyManager sharedHockeyManager].crashManager setAlertViewHandler:^(){ - NSString *exceptionReason = [[BITHockeyManager sharedHockeyManager].crashManager lastSessionCrashDetails].exceptionReason; - UIAlertView *customAlertView = [[UIAlertView alloc] initWithTitle: @"Oh no! The App crashed" - message: nil - delegate: self - cancelButtonTitle: @"Don't send" - otherButtonTitles: @"Send", @"Always send", nil]; - if (exceptionReason) { - customAlertView.message = @"We would like to send a crash report to the developers. Please enter a short description of what happened:"; - customAlertView.alertViewStyle = UIAlertViewStylePlainTextInput; - } else { - customAlertView.message = @"We would like to send a crash report to the developers"; - } - - [customAlertView show]; - }]; - - [[BITHockeyManager sharedHockeyManager].authenticator authenticateInstallation]; - - return YES; -} - -- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex { - BITCrashMetaData *crashMetaData = [BITCrashMetaData new]; - if (alertView.alertViewStyle != UIAlertViewStyleDefault) { - crashMetaData.userDescription = [alertView textFieldAtIndex:0].text; - } - switch (buttonIndex) { - case 0: - [[BITHockeyManager sharedHockeyManager].crashManager handleUserInput:BITCrashManagerUserInputDontSend withUserProvidedMetaData:nil]; - break; - case 1: - [[BITHockeyManager sharedHockeyManager].crashManager handleUserInput:BITCrashManagerUserInputSend withUserProvidedMetaData:crashMetaData]; - break; - case 2: - [[BITHockeyManager sharedHockeyManager].crashManager handleUserInput:BITCrashManagerUserInputAlwaysSend withUserProvidedMetaData:crashMetaData]; - break; - } -} - -@end -``` - -**Swift** - -```swift -import UIKit -import HockeySDK - -@UIApplicationMain -class AppDelegate: UIResponder, UIApplicationDelegate, UIAlertViewDelegate { - - var window: UIWindow? - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { - window?.makeKeyAndVisible() - - BITHockeyManager.shared().configure(withIdentifier: "APP_IDENTIFIER") - // optionally enable logging to get more information about states. - BITHockeyManager.shared().logLevel = BITLogLevel.verbose - - BITHockeyManager.shared().crashManager.setAlertViewHandler { - let exceptionReason = BITHockeyManager.shared().crashManager.lastSessionCrashDetails.exceptionReason - let customAlertView = UIAlertView.init(title: "Oh no! The App crashed", - message: "The App crashed", - delegate: self, - cancelButtonTitle: "Don't send", - otherButtonTitles: "Send", "Always send") - if (exceptionReason != nil) { - customAlertView.message = "We would like to send a crash report to the developers. Please enter a short description of what happened:" - customAlertView.alertViewStyle = UIAlertViewStyle.plainTextInput; - } else { - customAlertView.message = "We would like to send a crash report to the developers" - } - customAlertView.show() - } - - return true - } - - func alertView(_ alertView: UIAlertView, didDismissWithButtonIndex buttonIndex: Int) { - let crashMetaData = BITCrashMetaData(); - if (alertView.alertViewStyle != UIAlertViewStyle.default) { - crashMetaData.userProvidedDescription = alertView.textField(at: 0)?.text - } - switch (buttonIndex) { - case 0: - BITHockeyManager.shared().crashManager.handle(BITCrashManagerUserInput.dontSend, withUserProvidedMetaData: nil) - case 1: - BITHockeyManager.shared().crashManager.handle(BITCrashManagerUserInput.send, withUserProvidedMetaData: crashMetaData) - case 2: - BITHockeyManager.shared().crashManager.handle(BITCrashManagerUserInput.alwaysSend, withUserProvidedMetaData: crashMetaData) - } - } -} -``` - - diff --git a/submodules/HockeySDK-iOS/Documentation/Guides/Upload Symbols.md b/submodules/HockeySDK-iOS/Documentation/Guides/Upload Symbols.md deleted file mode 100644 index 8b474a0710..0000000000 --- a/submodules/HockeySDK-iOS/Documentation/Guides/Upload Symbols.md +++ /dev/null @@ -1,25 +0,0 @@ -## Introduction - -Mac and iOS crash reports show the stack traces for all running threads of your app of the time a crash occurred. But the stack traces only contain memory addresses and don't show class names, methods, file names and line numbers that are needed to understand them. - -To get these memory addresses translated you need to upload a dSYM package to the server, which contains all information required to make this happen. The symbolication process will then check the binary images section of the crash report and grab the UUID of the binary that caused the crash. Next it will get the UUID of the dSYM package to make sure they are identical and process the data if so. - -**WARNING:** Every time you are doing a build, the app binary and the dSYM will get a new unique UUID, no matter if you changed the code or not. So make sure to archive all your binaries and dSYMs that you are using for beta or app store builds! -This will also apply when using Bitcode. Then, Apple will use your uploaded build and re-compile it on their end. Whenever this happens, this also changes the UUID and requires you to download the newly generated dSYM from Apple and upload it to HockeyApp. - -## HowTo - -Once you have your app ready for beta testing or even to submit it to the App Store, you need to upload the `.dSYM` bundle to HockeyApp to enable symbolication. If you have built your app with Xcode, menu `Product` > `Archive`, you can find the `.dSYM` as follows: - -1. Chose `Window` > `Organizer` in Xcode. -2. Select the tab Archives. -3. Select your app in the left sidebar. -4. Right-click on the latest archive and select `Show in Finder`. -5. Right-click the `.xcarchive` in Finder and select `Show Package Contents`. -6. You should see a folder named dSYMs which contains your dSYM bundle. If you use Safari, just drag this file from Finder and drop it on to the corresponding drop zone in HockeyApp. If you use another browser, copy the file to a different location, then right-click it and choose Compress `YourApp.dSYM`. The file will be compressed as a .zip file. Drag & drop this file to HockeyApp. - -## Mac Desktop Uploader - -As an alternative, you can use our [HockeyApp for Mac](http://hockeyapp.net/releases/mac/) app to upload the complete archive in one step. - -Also check out the guide on [how to upload to HockeyApp from Mac OS X](http://support.hockeyapp.net/kb/client-integration-ios-mac-os-x/how-to-upload-to-hockeyapp-from-mac-os-x). \ No newline at end of file diff --git a/submodules/HockeySDK-iOS/Documentation/HockeySDK/.jazzy.yaml b/submodules/HockeySDK-iOS/Documentation/HockeySDK/.jazzy.yaml deleted file mode 100644 index 5e5c34f9c8..0000000000 --- a/submodules/HockeySDK-iOS/Documentation/HockeySDK/.jazzy.yaml +++ /dev/null @@ -1,39 +0,0 @@ -objc: true -clean: true -sdk: iphonesimulator - -theme: ../Themes/apple - -module: HockeySDK -module_version: 5.1.2 -author: Microsoft Corp -author_url: https://www.microsoft.com - -readme: ../../README.md -documentation: ../Guides/*.md -custom_categories: - - name: Guides - children: - - Installation & Setup - - Migration Kits - - name: How To - children: - - App Versioning - - Set Custom AlertViewHandler - - Upload Symbols - - name: Troubleshooting - children: - - Crash Reporting Not Working - - name: Release Notes - children: - - Changelog - -umbrella_header: ../../Classes/HockeySDK.h - -root_url: https://support.hockeyapp.net/kb/api -github_url: https://github.com/bitstadium/HockeySDK-iOS/ -github_file_prefix: "https://github.com/bitstadium/HockeySDK-iOS/" - -skip_undocumented: true - -output: Generated/ diff --git a/submodules/HockeySDK-iOS/Documentation/Themes/apple/assets/css/highlight.css.scss b/submodules/HockeySDK-iOS/Documentation/Themes/apple/assets/css/highlight.css.scss deleted file mode 100755 index 7bc1f2911f..0000000000 --- a/submodules/HockeySDK-iOS/Documentation/Themes/apple/assets/css/highlight.css.scss +++ /dev/null @@ -1,63 +0,0 @@ -/* Credit to https://gist.github.com/wataru420/2048287 */ - -.highlight { - .c { color: #999988; font-style: italic } /* Comment */ - .err { color: #a61717; background-color: #e3d2d2 } /* Error */ - .k { color: #000000; font-weight: bold } /* Keyword */ - .o { color: #000000; font-weight: bold } /* Operator */ - .cm { color: #999988; font-style: italic } /* Comment.Multiline */ - .cp { color: #999999; font-weight: bold } /* Comment.Preproc */ - .c1 { color: #999988; font-style: italic } /* Comment.Single */ - .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ - .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ - .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */ - .ge { color: #000000; font-style: italic } /* Generic.Emph */ - .gr { color: #aa0000 } /* Generic.Error */ - .gh { color: #999999 } /* Generic.Heading */ - .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ - .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */ - .go { color: #888888 } /* Generic.Output */ - .gp { color: #555555 } /* Generic.Prompt */ - .gs { font-weight: bold } /* Generic.Strong */ - .gu { color: #aaaaaa } /* Generic.Subheading */ - .gt { color: #aa0000 } /* Generic.Traceback */ - .kc { color: #000000; font-weight: bold } /* Keyword.Constant */ - .kd { color: #000000; font-weight: bold } /* Keyword.Declaration */ - .kp { color: #000000; font-weight: bold } /* Keyword.Pseudo */ - .kr { color: #000000; font-weight: bold } /* Keyword.Reserved */ - .kt { color: #445588; } /* Keyword.Type */ - .m { color: #009999 } /* Literal.Number */ - .s { color: #d14 } /* Literal.String */ - .na { color: #008080 } /* Name.Attribute */ - .nb { color: #0086B3 } /* Name.Builtin */ - .nc { color: #445588; font-weight: bold } /* Name.Class */ - .no { color: #008080 } /* Name.Constant */ - .ni { color: #800080 } /* Name.Entity */ - .ne { color: #990000; font-weight: bold } /* Name.Exception */ - .nf { color: #990000; } /* Name.Function */ - .nn { color: #555555 } /* Name.Namespace */ - .nt { color: #000080 } /* Name.Tag */ - .nv { color: #008080 } /* Name.Variable */ - .ow { color: #000000; font-weight: bold } /* Operator.Word */ - .w { color: #bbbbbb } /* Text.Whitespace */ - .mf { color: #009999 } /* Literal.Number.Float */ - .mh { color: #009999 } /* Literal.Number.Hex */ - .mi { color: #009999 } /* Literal.Number.Integer */ - .mo { color: #009999 } /* Literal.Number.Oct */ - .sb { color: #d14 } /* Literal.String.Backtick */ - .sc { color: #d14 } /* Literal.String.Char */ - .sd { color: #d14 } /* Literal.String.Doc */ - .s2 { color: #d14 } /* Literal.String.Double */ - .se { color: #d14 } /* Literal.String.Escape */ - .sh { color: #d14 } /* Literal.String.Heredoc */ - .si { color: #d14 } /* Literal.String.Interpol */ - .sx { color: #d14 } /* Literal.String.Other */ - .sr { color: #009926 } /* Literal.String.Regex */ - .s1 { color: #d14 } /* Literal.String.Single */ - .ss { color: #990073 } /* Literal.String.Symbol */ - .bp { color: #999999 } /* Name.Builtin.Pseudo */ - .vc { color: #008080 } /* Name.Variable.Class */ - .vg { color: #008080 } /* Name.Variable.Global */ - .vi { color: #008080 } /* Name.Variable.Instance */ - .il { color: #009999 } /* Literal.Number.Integer.Long */ -} diff --git a/submodules/HockeySDK-iOS/Documentation/Themes/apple/assets/css/jazzy.css.scss b/submodules/HockeySDK-iOS/Documentation/Themes/apple/assets/css/jazzy.css.scss deleted file mode 100755 index 2cc89840ea..0000000000 --- a/submodules/HockeySDK-iOS/Documentation/Themes/apple/assets/css/jazzy.css.scss +++ /dev/null @@ -1,480 +0,0 @@ -//////////////////////////////// -// Constants -//////////////////////////////// - -$bg_color: #414141; -$doc_coverage_color: #999; -$code_color: #777; -$code_bg_color: #eee; -$link_color: #0088cc; -$white_color: #fff; -$light_gray_bg_color: #f2f2f2; -$declaration_bg_color: #f9f9f9; -$sidebar_bg_color: #f9f9f9; -$declaration_title_language_color: #4b8afb; - -$sidebar_width: 230px; -$content_wrapper_width: 980px; -$content_top_offset: 70px; -$content_body_margin: 16px; -$content_body_left_offset: $sidebar_width + $content_body_margin; -$header_height: 26px; -$breadcrumb_padding_top: 17px; - -$code_font: 0.95em Menlo, monospace; - -$gray_border: 1px solid #e2e2e2; -$declaration_language_border: 5px solid #cde9f4; - -$aside_color: #aaa; -$aside_border: 5px solid lighten($aside_color, 20%); -$aside_warning_color: #ff0000; -$aside_warning_border: 5px solid lighten($aside_warning_color, 20%); - -//////////////////////////////// -// Reset -//////////////////////////////// - -html, body, div, span, h1, h3, h4, p, a, code, em, img, ul, li, table, tbody, tr, td { - background: transparent; - border: 0; - margin: 0; - outline: 0; - padding: 0; - vertical-align: baseline; -} - -//////////////////////////////// -// Global -//////////////////////////////// - -body { - background-color: $light_gray_bg_color; - font-family: Helvetica, freesans, Arial, sans-serif; - font-size: 14px; - -webkit-font-smoothing: subpixel-antialiased; - word-wrap: break-word; -} - -// Headers - -h1, h2, h3 { - margin-top: 0.8em; - margin-bottom: 0.3em; - font-weight: 100; - color: black; -} -h1 { - font-size: 2.5em; -} -h2 { - font-size: 2em; - border-bottom: $gray_border; -} -h4 { - font-size: 13px; - line-height: 1.5; - margin-top: 21px; -} -h5 { - font-size: 1.1em; -} -h6 { - font-size: 1.1em; - color: $code_color; -} -.section-name { - color: rgba(128,128,128,1); - display: block; - font-family: Helvetica; - font-size: 22px; - font-weight: 100; - margin-bottom: 15px; -} - -// Code - -pre, code { - font: $code_font; - color: $code_color; - word-wrap: normal; -} -p code, li code { - background-color: $code_bg_color; - padding: 2px 4px; - border-radius: 4px; -} - -// Links - -a { - color: $link_color; - text-decoration: none; -} - -// Lists - -ul { - padding-left: 15px; -} -li { - line-height: 1.8em; -} - -// Images - -img { - max-width: 100%; -} - -// Blockquotes - -blockquote { - margin-left: 0; - padding: 0 10px; - border-left: 4px solid #ccc; -} - -// General Content Wrapper - -.content-wrapper { - margin: 0 auto; - width: $content_wrapper_width; -} - -//////////////////////////////// -// Header & Top Breadcrumbs -//////////////////////////////// - -header { - font-size: 0.85em; - line-height: $header_height; - background-color: $bg_color; - position: fixed; - width: 100%; - z-index: 1; - img { - padding-right: 6px; - vertical-align: -4px; - height: 16px; - } - a { - color: $white_color; - } - p { - float: left; - color: $doc_coverage_color; - } - .header-right { - float: right; - margin-left: 16px; - } -} - -#breadcrumbs { - background-color: $light_gray_bg_color; - height: $content_top_offset - $header_height - $breadcrumb_padding_top; - padding-top: $breadcrumb_padding_top; - position: fixed; - width: 100%; - z-index: 1; - margin-top: $header_height; - #carat { - height: 10px; - margin: 0 5px; - } -} - -//////////////////////////////// -// Side Navigation -//////////////////////////////// - -.sidebar { - background-color: $sidebar_bg_color; - border: $gray_border; - overflow-y: auto; - overflow-x: hidden; - position: fixed; - top: $content_top_offset; - bottom: 0; - width: $sidebar_width; - word-wrap: normal; -} - -.nav-groups { - list-style-type: none; - background: $white_color; - padding-left: 0; -} - -.nav-group-name { - border-bottom: $gray_border; - font-size: 1.1em; - font-weight: 100; - padding: 15px 0 15px 20px; - > a { - color: #333; - } -} - -.nav-group-tasks { - margin-top: 5px; -} - -.nav-group-task { - font-size: 0.9em; - list-style-type: none; - white-space: nowrap; - a { - color: #888; - } -} - -//////////////////////////////// -// Main Content -//////////////////////////////// - -.main-content { - background-color: $white_color; - border: $gray_border; - margin-left: $content_body_left_offset; - position: absolute; - overflow: hidden; - padding-bottom: 60px; - top: $content_top_offset; - width: $content_wrapper_width - $content_body_left_offset; - p, a, code, em, ul, table, blockquote { - margin-bottom: 1em; - } - p { - line-height: 1.8em; - } - section { - .section:first-child { - margin-top: 0; - padding-top: 0; - } - - .task-group-section .task-group:first-of-type { - padding-top: 10px; - - .section-name { - padding-top: 15px; - } - } - } -} - -.section { - padding: 0 25px; -} - -.highlight { - background-color: $code_bg_color; - padding: 10px 12px; - border: $gray_border; - border-radius: 4px; - overflow-x: auto; -} - -.declaration .highlight { - overflow-x: initial; // This allows the scrollbar to show up inside declarations - padding: 0 40px 40px 0; - margin-bottom: -25px; - background-color: transparent; - border: none; -} - -.section-name { - margin: 0; - margin-left: 18px; -} - -.task-group-section { - padding-left: 6px; - border-top: $gray_border; -} - -.task-group { - padding-top: 0px; -} - -.task-name-container { - a[name] { - &:before { - content: ""; - display: block; - padding-top: $content_top_offset; - margin: -$content_top_offset 0 0; - } - } -} - -.item { - padding-top: 8px; - width: 100%; - list-style-type: none; - a[name] { - &:before { - content: ""; - display: block; - padding-top: $content_top_offset; - margin: -$content_top_offset 0 0; - } - } - code { - background-color: transparent; - padding: 0; - } - .token { - padding-left: 3px; - margin-left: 15px; - font-size: 11.9px; - } - .declaration-note { - font-size: .85em; - color: rgba(128,128,128,1); - font-style: italic; - } -} - -.pointer-container { - border-bottom: $gray_border; - left: -23px; - padding-bottom: 13px; - position: relative; - width: 110%; -} - -.pointer { - background: $declaration_bg_color; - border-left: $gray_border; - border-top: $gray_border; - height: 12px; - left: 21px; - top: -7px; - -webkit-transform: rotate(45deg); - -moz-transform: rotate(45deg); - -o-transform: rotate(45deg); - transform: rotate(45deg); - position: absolute; - width: 12px; -} - -.height-container { - display: none; - left: -25px; - padding: 0 25px; - position: relative; - width: 100%; - overflow: hidden; - .section { - background: $declaration_bg_color; - border-bottom: $gray_border; - left: -25px; - position: relative; - width: 100%; - padding-top: 10px; - padding-bottom: 5px; - } -} - -.aside, .language { - padding: 6px 12px; - margin: 12px 0; - border-left: $aside_border; - overflow-y: hidden; - .aside-title { - font-size: 9px; - letter-spacing: 2px; - text-transform: uppercase; - padding-bottom: 0; - margin: 0; - color: $aside_color; - -webkit-user-select: none; - } - p:last-child { - margin-bottom: 0; - } -} - -.language { - border-left: $declaration_language_border; - .aside-title { - color: $declaration_title_language_color; - } -} - -.aside-warning { - border-left: $aside_warning_border; - .aside-title { - color: $aside_warning_color; - } -} - -.graybox { - border-collapse: collapse; - width: 100%; - p { - margin: 0; - word-break: break-word; - min-width: 50px; - } - td { - border: $gray_border; - padding: 5px 25px 5px 10px; - vertical-align: middle; - } - tr td:first-of-type { - text-align: right; - padding: 7px; - vertical-align: top; - word-break: normal; - width: 40px; - } -} - -.slightly-smaller { - font-size: 0.9em; -} - -#footer { - position: absolute; - bottom: 10px; - margin-left: 25px; - p { - margin: 0; - color: #aaa; - font-size: 0.8em; - } -} - -//////////////////////////////// -// Dash -//////////////////////////////// - -html.dash { - header, #breadcrumbs, .sidebar { - display: none; - } - .main-content { - width: $content_wrapper_width; - margin-left: 0; - border: none; - width: 100%; - top: 0; - padding-bottom: 0; - } - .height-container { - display: block; - } - .item .token { - margin-left: 0; - } - .content-wrapper { - width: auto; - } - #footer { - position: static; - } -} diff --git a/submodules/HockeySDK-iOS/Documentation/Themes/apple/assets/img/carat.png b/submodules/HockeySDK-iOS/Documentation/Themes/apple/assets/img/carat.png deleted file mode 100755 index 29d2f7fd49..0000000000 Binary files a/submodules/HockeySDK-iOS/Documentation/Themes/apple/assets/img/carat.png and /dev/null differ diff --git a/submodules/HockeySDK-iOS/Documentation/Themes/apple/assets/img/dash.png b/submodules/HockeySDK-iOS/Documentation/Themes/apple/assets/img/dash.png deleted file mode 100755 index 6f694c7a01..0000000000 Binary files a/submodules/HockeySDK-iOS/Documentation/Themes/apple/assets/img/dash.png and /dev/null differ diff --git a/submodules/HockeySDK-iOS/Documentation/Themes/apple/assets/img/gh.png b/submodules/HockeySDK-iOS/Documentation/Themes/apple/assets/img/gh.png deleted file mode 100755 index 628da97c70..0000000000 Binary files a/submodules/HockeySDK-iOS/Documentation/Themes/apple/assets/img/gh.png and /dev/null differ diff --git a/submodules/HockeySDK-iOS/Documentation/Themes/apple/assets/js/jazzy.js b/submodules/HockeySDK-iOS/Documentation/Themes/apple/assets/js/jazzy.js deleted file mode 100755 index 4ff9455b6b..0000000000 --- a/submodules/HockeySDK-iOS/Documentation/Themes/apple/assets/js/jazzy.js +++ /dev/null @@ -1,40 +0,0 @@ -window.jazzy = {'docset': false} -if (typeof window.dash != 'undefined') { - document.documentElement.className += ' dash' - window.jazzy.docset = true -} -if (navigator.userAgent.match(/xcode/i)) { - document.documentElement.className += ' xcode' - window.jazzy.docset = true -} - -// On doc load, toggle the URL hash discussion if present -$(document).ready(function() { - if (!window.jazzy.docset) { - var linkToHash = $('a[href="' + window.location.hash +'"]'); - linkToHash.trigger("click"); - } -}); - -// On token click, toggle its discussion and animate token.marginLeft -$(".token").click(function(event) { - if (window.jazzy.docset) { - return; - } - var link = $(this); - var animationDuration = 300; - var tokenOffset = "15px"; - var original = link.css('marginLeft') == tokenOffset; - link.animate({'margin-left':original ? "0px" : tokenOffset}, animationDuration); - $content = link.parent().parent().next(); - $content.slideToggle(animationDuration); - - // Keeps the document from jumping to the hash. - var href = $(this).attr('href'); - if (history.pushState) { - history.pushState({}, '', href); - } else { - location.hash = href; - } - event.preventDefault(); -}); diff --git a/submodules/HockeySDK-iOS/Documentation/Themes/apple/assets/js/jquery.min.js b/submodules/HockeySDK-iOS/Documentation/Themes/apple/assets/js/jquery.min.js deleted file mode 100755 index ab28a24729..0000000000 --- a/submodules/HockeySDK-iOS/Documentation/Themes/apple/assets/js/jquery.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery v1.11.1 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ -!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l="1.11.1",m=function(a,b){return new m.fn.init(a,b)},n=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,o=/^-ms-/,p=/-([\da-z])/gi,q=function(a,b){return b.toUpperCase()};m.fn=m.prototype={jquery:l,constructor:m,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=m.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return m.each(this,a,b)},map:function(a){return this.pushStack(m.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},m.extend=m.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||m.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(m.isPlainObject(c)||(b=m.isArray(c)))?(b?(b=!1,f=a&&m.isArray(a)?a:[]):f=a&&m.isPlainObject(a)?a:{},g[d]=m.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},m.extend({expando:"jQuery"+(l+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===m.type(a)},isArray:Array.isArray||function(a){return"array"===m.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return!m.isArray(a)&&a-parseFloat(a)>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==m.type(a)||a.nodeType||m.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(k.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&m.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(o,"ms-").replace(p,q)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=r(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(n,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(r(Object(a))?m.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=r(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),m.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||m.guid++,e):void 0},now:function(){return+new Date},support:k}),m.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function r(a){var b=a.length,c=m.type(a);return"function"===c||m.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var s=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+-new Date,v=a.document,w=0,x=0,y=gb(),z=gb(),A=gb(),B=function(a,b){return a===b&&(l=!0),0},C="undefined",D=1<<31,E={}.hasOwnProperty,F=[],G=F.pop,H=F.push,I=F.push,J=F.slice,K=F.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},L="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",N="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=N.replace("w","w#"),P="\\["+M+"*("+N+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+O+"))|)"+M+"*\\]",Q=":("+N+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+P+")*)|.*)\\)|)",R=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),S=new RegExp("^"+M+"*,"+M+"*"),T=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),V=new RegExp(Q),W=new RegExp("^"+O+"$"),X={ID:new RegExp("^#("+N+")"),CLASS:new RegExp("^\\.("+N+")"),TAG:new RegExp("^("+N.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+Q),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+L+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{I.apply(F=J.call(v.childNodes),v.childNodes),F[v.childNodes.length].nodeType}catch(eb){I={apply:F.length?function(a,b){H.apply(a,J.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],!a||"string"!=typeof a)return d;if(1!==(k=b.nodeType)&&9!==k)return[];if(p&&!e){if(f=_.exec(a))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return I.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return I.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=9===k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+qb(o[l]);w=ab.test(a)&&ob(b.parentNode)||b,x=o.join(",")}if(x)try{return I.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function gb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function hb(a){return a[u]=!0,a}function ib(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function jb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function kb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||D)-(~a.sourceIndex||D);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function lb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function nb(a){return hb(function(b){return b=+b,hb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function ob(a){return a&&typeof a.getElementsByTagName!==C&&a}c=fb.support={},f=fb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fb.setDocument=function(a){var b,e=a?a.ownerDocument||a:v,g=e.defaultView;return e!==n&&9===e.nodeType&&e.documentElement?(n=e,o=e.documentElement,p=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){m()},!1):g.attachEvent&&g.attachEvent("onunload",function(){m()})),c.attributes=ib(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ib(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(e.getElementsByClassName)&&ib(function(a){return a.innerHTML="
",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=ib(function(a){return o.appendChild(a).id=u,!e.getElementsByName||!e.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==C&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c=typeof a.getAttributeNode!==C&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==C?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==C&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(e.querySelectorAll))&&(ib(function(a){a.innerHTML="",a.querySelectorAll("[msallowclip^='']").length&&q.push("[*^$]="+M+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+M+"*(?:value|"+L+")"),a.querySelectorAll(":checked").length||q.push(":checked")}),ib(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+M+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ib(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",Q)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===v&&t(v,a)?-1:b===e||b.ownerDocument===v&&t(v,b)?1:k?K.call(k,a)-K.call(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],i=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:k?K.call(k,a)-K.call(k,b):0;if(f===g)return kb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?kb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},e):n},fb.matches=function(a,b){return fb(a,null,null,b)},fb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fb(b,n,null,[a]).length>0},fb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&E.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fb.selectors={cacheLength:50,createPseudo:hb,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+M+")"+a+"("+M+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==C&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?hb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=K.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:hb(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?hb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:hb(function(a){return function(b){return fb(a,b).length>0}}),contains:hb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:hb(function(a){return W.test(a||"")||fb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:nb(function(){return[0]}),last:nb(function(a,b){return[b-1]}),eq:nb(function(a,b,c){return[0>c?c+b:c]}),even:nb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:nb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:nb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:nb(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function rb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function sb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function tb(a,b,c){for(var d=0,e=b.length;e>d;d++)fb(a,b[d],c);return c}function ub(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function vb(a,b,c,d,e,f){return d&&!d[u]&&(d=vb(d)),e&&!e[u]&&(e=vb(e,f)),hb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||tb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ub(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ub(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?K.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ub(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):I.apply(g,r)})}function wb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=rb(function(a){return a===b},h,!0),l=rb(function(a){return K.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>i;i++)if(c=d.relative[a[i].type])m=[rb(sb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return vb(i>1&&sb(m),i>1&&qb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&wb(a.slice(i,e)),f>e&&wb(a=a.slice(e)),f>e&&qb(a))}m.push(c)}return sb(m)}function xb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=G.call(i));s=ub(s)}I.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&fb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?hb(f):f}return h=fb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xb(e,d)),f.selector=a}return f},i=fb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&ob(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qb(j),!a)return I.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&ob(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ib(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ib(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||jb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ib(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||jb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ib(function(a){return null==a.getAttribute("disabled")})||jb(L,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fb}(a);m.find=s,m.expr=s.selectors,m.expr[":"]=m.expr.pseudos,m.unique=s.uniqueSort,m.text=s.getText,m.isXMLDoc=s.isXML,m.contains=s.contains;var t=m.expr.match.needsContext,u=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,v=/^.[^:#\[\.,]*$/;function w(a,b,c){if(m.isFunction(b))return m.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return m.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(v.test(b))return m.filter(b,a,c);b=m.filter(b,a)}return m.grep(a,function(a){return m.inArray(a,b)>=0!==c})}m.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?m.find.matchesSelector(d,a)?[d]:[]:m.find.matches(a,m.grep(b,function(a){return 1===a.nodeType}))},m.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(m(a).filter(function(){for(b=0;e>b;b++)if(m.contains(d[b],this))return!0}));for(b=0;e>b;b++)m.find(a,d[b],c);return c=this.pushStack(e>1?m.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(w(this,a||[],!1))},not:function(a){return this.pushStack(w(this,a||[],!0))},is:function(a){return!!w(this,"string"==typeof a&&t.test(a)?m(a):a||[],!1).length}});var x,y=a.document,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=m.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||x).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof m?b[0]:b,m.merge(this,m.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:y,!0)),u.test(c[1])&&m.isPlainObject(b))for(c in b)m.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=y.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return x.find(a);this.length=1,this[0]=d}return this.context=y,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):m.isFunction(a)?"undefined"!=typeof x.ready?x.ready(a):a(m):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),m.makeArray(a,this))};A.prototype=m.fn,x=m(y);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};m.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!m(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),m.fn.extend({has:function(a){var b,c=m(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(m.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=t.test(a)||"string"!=typeof a?m(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&m.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?m.unique(f):f)},index:function(a){return a?"string"==typeof a?m.inArray(this[0],m(a)):m.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(m.unique(m.merge(this.get(),m(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}m.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return m.dir(a,"parentNode")},parentsUntil:function(a,b,c){return m.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return m.dir(a,"nextSibling")},prevAll:function(a){return m.dir(a,"previousSibling")},nextUntil:function(a,b,c){return m.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return m.dir(a,"previousSibling",c)},siblings:function(a){return m.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return m.sibling(a.firstChild)},contents:function(a){return m.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:m.merge([],a.childNodes)}},function(a,b){m.fn[a]=function(c,d){var e=m.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=m.filter(d,e)),this.length>1&&(C[a]||(e=m.unique(e)),B.test(a)&&(e=e.reverse())),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return m.each(a.match(E)||[],function(a,c){b[c]=!0}),b}m.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):m.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){m.each(b,function(b,c){var d=m.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&m.each(arguments,function(a,c){var d;while((d=m.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?m.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},m.extend({Deferred:function(a){var b=[["resolve","done",m.Callbacks("once memory"),"resolved"],["reject","fail",m.Callbacks("once memory"),"rejected"],["notify","progress",m.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return m.Deferred(function(c){m.each(b,function(b,f){var g=m.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&m.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?m.extend(a,d):d}},e={};return d.pipe=d.then,m.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&m.isFunction(a.promise)?e:0,g=1===f?a:m.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&m.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;m.fn.ready=function(a){return m.ready.promise().done(a),this},m.extend({isReady:!1,readyWait:1,holdReady:function(a){a?m.readyWait++:m.ready(!0)},ready:function(a){if(a===!0?!--m.readyWait:!m.isReady){if(!y.body)return setTimeout(m.ready);m.isReady=!0,a!==!0&&--m.readyWait>0||(H.resolveWith(y,[m]),m.fn.triggerHandler&&(m(y).triggerHandler("ready"),m(y).off("ready")))}}});function I(){y.addEventListener?(y.removeEventListener("DOMContentLoaded",J,!1),a.removeEventListener("load",J,!1)):(y.detachEvent("onreadystatechange",J),a.detachEvent("onload",J))}function J(){(y.addEventListener||"load"===event.type||"complete"===y.readyState)&&(I(),m.ready())}m.ready.promise=function(b){if(!H)if(H=m.Deferred(),"complete"===y.readyState)setTimeout(m.ready);else if(y.addEventListener)y.addEventListener("DOMContentLoaded",J,!1),a.addEventListener("load",J,!1);else{y.attachEvent("onreadystatechange",J),a.attachEvent("onload",J);var c=!1;try{c=null==a.frameElement&&y.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!m.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}I(),m.ready()}}()}return H.promise(b)};var K="undefined",L;for(L in m(k))break;k.ownLast="0"!==L,k.inlineBlockNeedsLayout=!1,m(function(){var a,b,c,d;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",k.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(d))}),function(){var a=y.createElement("div");if(null==k.deleteExpando){k.deleteExpando=!0;try{delete a.test}catch(b){k.deleteExpando=!1}}a=null}(),m.acceptData=function(a){var b=m.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var M=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,N=/([A-Z])/g;function O(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(N,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:M.test(c)?m.parseJSON(c):c}catch(e){}m.data(a,b,c)}else c=void 0}return c}function P(a){var b;for(b in a)if(("data"!==b||!m.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h; -if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML="
a",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function ab(){return!0}function bb(){return!1}function cb(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(E)||[""],h=b.length;while(h--)f=_.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=m.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=m.event.special[o]||{},l=m.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&m.expr.match.needsContext.test(e),namespace:p.join(".")},i),(n=g[o])||(n=g[o]=[],n.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?n.splice(n.delegateCount++,0,l):n.push(l),m.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m.hasData(a)&&m._data(a);if(r&&(k=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=_.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=m.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,n=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=n.length;while(f--)g=n[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(n.splice(f,1),g.selector&&n.delegateCount--,l.remove&&l.remove.call(a,g));i&&!n.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||m.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)m.event.remove(a,o+b[j],c,d,!0);m.isEmptyObject(k)&&(delete r.handle,m._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,o=[d||y],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||y,3!==d.nodeType&&8!==d.nodeType&&!$.test(p+m.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[m.expando]?b:new m.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:m.makeArray(c,[b]),k=m.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!m.isWindow(d)){for(i=k.delegateType||p,$.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||y)&&o.push(l.defaultView||l.parentWindow||a)}n=0;while((h=o[n++])&&!b.isPropagationStopped())b.type=n>1?i:k.bindType||p,f=(m._data(h,"events")||{})[b.type]&&m._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&m.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&m.acceptData(d)&&g&&d[p]&&!m.isWindow(d)){l=d[g],l&&(d[g]=null),m.event.triggered=p;try{d[p]()}catch(r){}m.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=m.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(m._data(this,"events")||{})[a.type]||[],k=m.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=m.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((m.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?m(c,this).index(i)>=0:m.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h]","i"),hb=/^\s+/,ib=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,jb=/<([\w:]+)/,kb=/\s*$/g,rb={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:k.htmlSerialize?[0,"",""]:[1,"X
","
"]},sb=db(y),tb=sb.appendChild(y.createElement("div"));rb.optgroup=rb.option,rb.tbody=rb.tfoot=rb.colgroup=rb.caption=rb.thead,rb.th=rb.td;function ub(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==K?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==K?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||m.nodeName(d,b)?f.push(d):m.merge(f,ub(d,b));return void 0===b||b&&m.nodeName(a,b)?m.merge([a],f):f}function vb(a){W.test(a.type)&&(a.defaultChecked=a.checked)}function wb(a,b){return m.nodeName(a,"table")&&m.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xb(a){return a.type=(null!==m.find.attr(a,"type"))+"/"+a.type,a}function yb(a){var b=pb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function zb(a,b){for(var c,d=0;null!=(c=a[d]);d++)m._data(c,"globalEval",!b||m._data(b[d],"globalEval"))}function Ab(a,b){if(1===b.nodeType&&m.hasData(a)){var c,d,e,f=m._data(a),g=m._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)m.event.add(b,c,h[c][d])}g.data&&(g.data=m.extend({},g.data))}}function Bb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!k.noCloneEvent&&b[m.expando]){e=m._data(b);for(d in e.events)m.removeEvent(b,d,e.handle);b.removeAttribute(m.expando)}"script"===c&&b.text!==a.text?(xb(b).text=a.text,yb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),k.html5Clone&&a.innerHTML&&!m.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&W.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}m.extend({clone:function(a,b,c){var d,e,f,g,h,i=m.contains(a.ownerDocument,a);if(k.html5Clone||m.isXMLDoc(a)||!gb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(tb.innerHTML=a.outerHTML,tb.removeChild(f=tb.firstChild)),!(k.noCloneEvent&&k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||m.isXMLDoc(a)))for(d=ub(f),h=ub(a),g=0;null!=(e=h[g]);++g)d[g]&&Bb(e,d[g]);if(b)if(c)for(h=h||ub(a),d=d||ub(f),g=0;null!=(e=h[g]);g++)Ab(e,d[g]);else Ab(a,f);return d=ub(f,"script"),d.length>0&&zb(d,!i&&ub(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,l,n=a.length,o=db(b),p=[],q=0;n>q;q++)if(f=a[q],f||0===f)if("object"===m.type(f))m.merge(p,f.nodeType?[f]:f);else if(lb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(jb.exec(f)||["",""])[1].toLowerCase(),l=rb[i]||rb._default,h.innerHTML=l[1]+f.replace(ib,"<$1>")+l[2],e=l[0];while(e--)h=h.lastChild;if(!k.leadingWhitespace&&hb.test(f)&&p.push(b.createTextNode(hb.exec(f)[0])),!k.tbody){f="table"!==i||kb.test(f)?""!==l[1]||kb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ub(p,"input"),vb),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ub(o.appendChild(f),"script"),g&&zb(h),c)){e=0;while(f=h[e++])ob.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ub(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&zb(ub(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ub(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fb,""):void 0;if(!("string"!=typeof a||mb.test(a)||!k.htmlSerialize&&gb.test(a)||!k.leadingWhitespace&&hb.test(a)||rb[(jb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ib,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ub(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,m.cleanData(ub(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&nb.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ub(i,"script"),xb),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ub(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,yb),j=0;f>j;j++)d=g[j],ob.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qb,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Cb,Db={};function Eb(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fb(a){var b=y,c=Db[a];return c||(c=Eb(a,b),"none"!==c&&c||(Cb=(Cb||m("